Librairies

Mise en place du dataset

data = read.csv(file = "notes.csv")
names(data) <- c('authentique', 'diagonale', 'hauteur_g', 'hauteur_d', 'marge_bas', 'marge_haut', 'longueur')
head(data, 1)
data_test = data
data_test$auth <- ifelse (data_test$authentique == "True", 1, 0)
data_ok = select(data_test, subset = -c(1))
rm(data_test)

#Mission 0 - Afin d’introduire votre analyse, effectuez une brève description des données (analyses univariées (descriptions, densité, histogramme, boxplot) et bivariées).

#Fonction pour observer rapidement les données, la distribution des variables quali et la distribution des variables quanti

data_test = data
data_test$authentique = factor(data_test$authentique, levels = c('True', 'False'), labels = c('vrai_billet', 'faux_billet')) #On vérifie les niveaux de la variable quali dans le dataframe d'origine, et on relabel
basic_eda <- function(data_test)
{
  glimpse(data_test)
  print(status(data_test))
  freq(data_test) 
  print(profiling_num(data_test))
  plot_num(data_test)
  describe(data_test)
}

basic_eda(data_test)
Rows: 170
Columns: 7
$ authentique <fct> vrai_billet, vrai_billet, vrai_billet, vrai_billet, vrai_billet, vrai_billet, vrai_billet, vrai_billet, vrai_billet, ~
$ diagonale   <dbl> 171.81, 171.67, 171.83, 171.80, 172.05, 172.57, 172.38, 171.58, 171.96, 172.14, 172.27, 172.07, 172.19, 171.82, 172.0~
$ hauteur_g   <dbl> 104.86, 103.74, 103.76, 103.78, 103.70, 104.65, 103.55, 103.65, 103.51, 104.34, 104.29, 103.64, 104.61, 103.78, 103.9~
$ hauteur_d   <dbl> 104.95, 103.70, 103.76, 103.65, 103.75, 104.44, 103.80, 103.37, 103.75, 104.20, 104.22, 103.67, 103.69, 103.76, 103.7~
$ marge_bas   <dbl> 4.52, 4.01, 4.40, 3.73, 5.04, 4.54, 3.97, 3.54, 4.06, 4.63, 3.89, 3.86, 4.00, 3.81, 3.81, 4.56, 4.07, 4.52, 3.60, 4.1~
$ marge_haut  <dbl> 2.89, 2.87, 2.88, 3.12, 2.27, 2.99, 2.90, 3.19, 3.33, 3.02, 3.53, 3.20, 3.26, 3.25, 3.24, 2.56, 2.92, 2.71, 3.50, 3.0~
$ longueur    <dbl> 112.83, 113.29, 113.84, 113.63, 113.55, 113.16, 113.30, 113.38, 113.53, 112.47, 113.50, 113.83, 112.91, 113.36, 113.4~
Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.
Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.
data_test 

 7  Variables      170  Observations
-------------------------------------------------------------------------------------------------------------------------------------------
authentique 
       n  missing distinct 
     170        0        2 
                                  
Value      vrai_billet faux_billet
Frequency          100          70
Proportion       0.588       0.412
-------------------------------------------------------------------------------------------------------------------------------------------
diagonale 
       n  missing distinct     Info     Mean      Gmd      .05      .10      .25      .50      .75      .90      .95 
     170        0       88        1    171.9   0.3422    171.5    171.6    171.7    171.9    172.1    172.3    172.5 

lowest : 171.04 171.13 171.35 171.38 171.43, highest: 172.53 172.57 172.59 172.75 173.01
-------------------------------------------------------------------------------------------------------------------------------------------
hauteur_g 
       n  missing distinct     Info     Mean      Gmd      .05      .10      .25      .50      .75      .90      .95 
     170        0       91        1    104.1   0.3411    103.6    103.7    103.8    104.1    104.3    104.5    104.6 

lowest : 103.23 103.47 103.49 103.51 103.52, highest: 104.60 104.61 104.65 104.72 104.86
-------------------------------------------------------------------------------------------------------------------------------------------
hauteur_d 
       n  missing distinct     Info     Mean      Gmd      .05      .10      .25      .50      .75      .90      .95 
     170        0       96        1    103.9   0.3749    103.4    103.5    103.7    104.0    104.2    104.3    104.4 

lowest : 103.14 103.25 103.29 103.31 103.34, highest: 104.50 104.64 104.83 104.86 104.95
-------------------------------------------------------------------------------------------------------------------------------------------
marge_bas 
       n  missing distinct     Info     Mean      Gmd      .05      .10      .25      .50      .75      .90      .95 
     170        0      123        1    4.612   0.7939    3.715    3.810    4.050    4.450    5.128    5.712    5.891 

lowest : 3.54 3.60 3.64 3.65 3.66, highest: 6.00 6.01 6.16 6.19 6.28
-------------------------------------------------------------------------------------------------------------------------------------------
marge_haut 
       n  missing distinct     Info     Mean      Gmd      .05      .10      .25      .50      .75      .90      .95 
     170        0       81        1     3.17   0.2655    2.800    2.890    3.012    3.170    3.330    3.470    3.591 

lowest : 2.27 2.56 2.70 2.71 2.75, highest: 3.63 3.65 3.66 3.67 3.68
-------------------------------------------------------------------------------------------------------------------------------------------
longueur 
       n  missing distinct     Info     Mean      Gmd      .05      .10      .25      .50      .75      .90      .95 
     170        0      129        1    112.6    1.038    110.8    111.3    111.9    112.8    113.3    113.6    113.7 

lowest : 109.97 110.31 110.48 110.53 110.61, highest: 113.83 113.84 113.87 113.92 113.98
-------------------------------------------------------------------------------------------------------------------------------------------

#On a 7 variable : 1 qualitative ("authentique", qui prends pour valeur 0 si le billet est faux et 1 sinon), et 6 qualitatives, qui mesurent différents aspects du billet, avec pour unité le millimètre, et 2 décimales après la virgule. 
# Les écarts-types des variables quantitatives sont très faibles (-1mm).
#Près de 59% des billets sont véritables (100 billets) contre 41% faux (70 billets).
#Pour un boxplot et densité de toutes les variables quanti, en fonction de la variable quali

df = data
fig1 <- df %>% plot_ly(x = ~authentique, y = ~diagonale, split = ~authentique, type = 'violin', box = list(visible = T), meanline = list(visible = T)) 
fig1 <- fig1 %>% layout(xaxis = list(title = "Authenticité"),yaxis = list(title = "Diagonale",zeroline = F))

fig2 <- df %>% plot_ly(x = ~authentique, y = ~hauteur_g, split = ~authentique, type = 'violin', box = list(visible = T), meanline = list(visible = T)) 
fig2 <- fig2 %>% layout(xaxis = list(title = "Authenticité"),yaxis = list(title = "Hauteur gauche",zeroline = F))

fig3 <- df %>% plot_ly(x = ~authentique, y = ~hauteur_d, split = ~authentique, type = 'violin', box = list(visible = T), meanline = list(visible = T)) 
fig3 <- fig3 %>% layout(xaxis = list(title = "Authenticité"),yaxis = list(title = "Hauteur droite",zeroline = F))

fig4 <- df %>% plot_ly(x = ~authentique, y = ~marge_bas, split = ~authentique, type = 'violin', box = list(visible = T), meanline = list(visible = T)) 
fig4 <- fig4 %>% layout(xaxis = list(title = "Authenticité"),yaxis = list(title = "Marge basse",zeroline = F))

fig5 <- df %>% plot_ly(x = ~authentique, y = ~marge_haut, split = ~authentique, type = 'violin', box = list(visible = T), meanline = list(visible = T)) 
fig5 <- fig5 %>% layout(xaxis = list(title = "Authenticité"),yaxis = list(title = "Marge haute",zeroline = F))

fig6 <- df %>% plot_ly(x = ~authentique, y = ~longueur, split = ~authentique, type = 'violin', box = list(visible = T), meanline = list(visible = T)) 
fig6 <- fig6 %>% layout(xaxis = list(title = "Authenticité"),yaxis = list(title = "Longueur",zeroline = F))

fig1
fig2
fig3
fig4
fig5
fig6
#Corrélogramme (analyse 2 par 2) des variables quanti en fonction de la variable quali, plus niveau de corrélation en général et en fonction de la variable quali, plus significativité du taux de corrélation

p <- ggpairs(data, 
             columns = 1:7, 
             ggplot2::aes(colour=authentique), 
             title="Corrélograme") 
p

 plot: [1,1] [=>-----------------------------------------------------------------------------------------------------------]  2% est: 0s 
 plot: [1,2] [===>---------------------------------------------------------------------------------------------------------]  4% est: 5s 
 plot: [1,3] [======>------------------------------------------------------------------------------------------------------]  6% est: 6s 
 plot: [1,4] [========>----------------------------------------------------------------------------------------------------]  8% est: 5s 
 plot: [1,5] [==========>--------------------------------------------------------------------------------------------------] 10% est: 5s 
 plot: [1,6] [============>------------------------------------------------------------------------------------------------] 12% est: 5s 
 plot: [1,7] [===============>---------------------------------------------------------------------------------------------] 14% est: 4s 
 plot: [2,1] [=================>-------------------------------------------------------------------------------------------] 16% est: 4s `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

 plot: [2,2] [===================>-----------------------------------------------------------------------------------------] 18% est: 4s 
 plot: [2,3] [=====================>---------------------------------------------------------------------------------------] 20% est: 4s 
 plot: [2,4] [=======================>-------------------------------------------------------------------------------------] 22% est: 4s 
 plot: [2,5] [==========================>----------------------------------------------------------------------------------] 24% est: 4s 
 plot: [2,6] [============================>--------------------------------------------------------------------------------] 27% est: 4s 
 plot: [2,7] [==============================>------------------------------------------------------------------------------] 29% est: 4s 
 plot: [3,1] [================================>----------------------------------------------------------------------------] 31% est: 4s `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

 plot: [3,2] [===================================>-------------------------------------------------------------------------] 33% est: 4s 
 plot: [3,3] [=====================================>-----------------------------------------------------------------------] 35% est: 4s 
 plot: [3,4] [=======================================>---------------------------------------------------------------------] 37% est: 3s 
 plot: [3,5] [=========================================>-------------------------------------------------------------------] 39% est: 3s 
 plot: [3,6] [===========================================>-----------------------------------------------------------------] 41% est: 3s 
 plot: [3,7] [==============================================>--------------------------------------------------------------] 43% est: 3s 
 plot: [4,1] [================================================>------------------------------------------------------------] 45% est: 3s `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

 plot: [4,2] [==================================================>----------------------------------------------------------] 47% est: 3s 
 plot: [4,3] [====================================================>--------------------------------------------------------] 49% est: 3s 
 plot: [4,4] [=======================================================>-----------------------------------------------------] 51% est: 3s 
 plot: [4,5] [=========================================================>---------------------------------------------------] 53% est: 2s 
 plot: [4,6] [===========================================================>-------------------------------------------------] 55% est: 2s 
 plot: [4,7] [=============================================================>-----------------------------------------------] 57% est: 2s 
 plot: [5,1] [================================================================>--------------------------------------------] 59% est: 2s `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

 plot: [5,2] [==================================================================>------------------------------------------] 61% est: 2s 
 plot: [5,3] [====================================================================>----------------------------------------] 63% est: 2s 
 plot: [5,4] [======================================================================>--------------------------------------] 65% est: 2s 
 plot: [5,5] [========================================================================>------------------------------------] 67% est: 2s 
 plot: [5,6] [===========================================================================>---------------------------------] 69% est: 2s 
 plot: [5,7] [=============================================================================>-------------------------------] 71% est: 1s 
 plot: [6,1] [===============================================================================>-----------------------------] 73% est: 1s `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

 plot: [6,2] [=================================================================================>---------------------------] 76% est: 1s 
 plot: [6,3] [====================================================================================>------------------------] 78% est: 1s 
 plot: [6,4] [======================================================================================>----------------------] 80% est: 1s 
 plot: [6,5] [========================================================================================>--------------------] 82% est: 1s 
 plot: [6,6] [==========================================================================================>------------------] 84% est: 1s 
 plot: [6,7] [============================================================================================>----------------] 86% est: 1s 
 plot: [7,1] [===============================================================================================>-------------] 88% est: 1s `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

 plot: [7,2] [=================================================================================================>-----------] 90% est: 1s 
 plot: [7,3] [===================================================================================================>---------] 92% est: 0s 
 plot: [7,4] [=====================================================================================================>-------] 94% est: 0s 
 plot: [7,5] [========================================================================================================>----] 96% est: 0s 
 plot: [7,6] [==========================================================================================================>--] 98% est: 0s 
 plot: [7,7] [=============================================================================================================]100% est: 0s 
                                                                                                                                         

data_num = select(data, subset = -c(1))

#Matrice des corrélations
# Calcul de la matrice
corr <- round(cor(data_num), 1)

# Calcul de la matrice de p-values de corrélation
p.mat <- cor_pmat(data_num)

# Visualiser le triangle inférieur de la matrice de corrélation & Barrer les coefficients non significatifs
corr.plot <- ggcorrplot(
  corr, hc.order = TRUE, type = "lower", outline.col = "white",
  p.mat = p.mat)
#corr.plot
ggplotly(corr.plot)
Warning in L$marker$color[idx] <- aes2plotly(data, params, "fill")[idx] :
  number of items to replace is not a multiple of replacement length
#Matrice de corrélation inversée
corrplot(corr, type="upper", order="hclust", tl.col="black", tl.srt=45)


# Calculer les coefficients de corrélation
cor.coef <- cor(data_num)
# Calculer les p-values de corrélation
cor.test.p <- function(x){
    FUN <- function(x, y) cor.test(x, y)[["p.value"]]
    z <- outer(
      colnames(x), 
      colnames(x), 
      Vectorize(function(i,j) FUN(x[,i], x[,j]))
    )
    dimnames(z) <- list(colnames(x), colnames(x))
    z
}

p <- cor.test.p(data_num)

# Créer la Heatmap
heatmaply_cor(
  cor.coef,
  k_col = 2, 
  k_row = 2,
  node_type = "scatter",
  point_size_mat = -log10(p), 
  point_size_name = "-log10(p-value)", #on log pour ramener les chiffres entre 0 et 1 pour faciliter l'interprétation & les ordres de grandeur 
  label_names = c("x", "y", "Correlation")
)
#Corrélation négative entre la longueur et toutes les autres variables SAUF la diagonale, qui est statistiquement significative
#Corrélation positive importante entre la hauteur gauche et la hauteur droite
rm(cor.coef, corr, corr.plot, df, fig1, fig2, fig3, fig4, fig5, fig6, p, p.mat)
rm(cor.test.p, basic_eda)

Mission 1 - Vous réaliserez une analyse en composantes principales de l’échantillon

#Calcul des individus moyens : moyenne des variables par catégories : True (1) / False (0)
indiv_moy = aggregate(data_num, list(data$auth), mean)
print(indiv_moy)
indiv_moy = select(indiv_moy, subset = -c(1))

#Calcul en amont des centroides
centroides = scale(data_ok[,-7], center = TRUE, scale = TRUE)
centroides = aggregate(centroides, list(data$auth), mean)
print(centroides)

rm(centroides, indiv_moy)
#ACP
#data_num_quant = select(data_num, subset = -c(7))
data_num_pca = PCA(data_num, scale.unit = TRUE, ncp = 7, graph = FALSE)
data_num_pca_var = get_pca_var(data_num_pca)
data_num_pca_ind = get_pca_ind(data_num_pca)
eig_val = get_eigenvalue(data_num_pca) 
#Extraction des valeurs propres / variances des composantes principales - La proportion de variance expliquée par chaque valeur propre est donnée dans la deuxième colonne. Le pourcentage cumulé expliqué est obtenu en ajoutant les proportions successives de variances expliquées. Les valeurs propres peuvent être utilisées pour déterminer le nombre d’axes principaux à conserver après l’ACP (Kaiser 1961)
eig_val
      eigenvalue variance.percent cumulative.variance.percent
Dim.1  2.8468752        47.447921                    47.44792
Dim.2  1.3174264        21.957106                    69.40503
Dim.3  0.8540715        14.234524                    83.63955
Dim.4  0.5115777         8.526295                    92.16585
Dim.5  0.2767693         4.612822                    96.77867
Dim.6  0.1932799         3.221331                   100.00000
fviz_eig(data_num_pca, addlabels = TRUE, ylim = c(0, 70)) #Visualisation des valeurs propres

get_pca_var(data_num_pca)#Extraction des résultats pour les les variables
Principal Component Analysis Results for variables
 ===================================================
  Name       Description                                    
1 "$coord"   "Coordinates for the variables"                
2 "$cor"     "Correlations between variables and dimensions"
3 "$cos2"    "Cos2 for the variables"                       
4 "$contrib" "contributions of the variables"               
#fviz_pca_var(data_num_pca)#visualisation des résultats des variables
#cos2 : qualité de représentation

var <- get_pca_var(data_num_pca)
corrplot(var$cos2, is.corr=TRUE)


fviz_pca_var(data_num_pca, 
             title='Cercle de corrélation',
             col.var = "cos2",
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
             repel = TRUE # Évite le chevauchement de texte
             )

fviz_pca_var(data_num_pca,
             title='Cercle de contribution',
             col.var = "contrib",
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07")
             )

get_pca_ind(data_num_pca)#Extraction des résultats pour les individus
Principal Component Analysis Results for individuals
 ===================================================
  Name       Description                       
1 "$coord"   "Coordinates for the individuals" 
2 "$cos2"    "Cos2 for the individuals"        
3 "$contrib" "contributions of the individuals"
#fviz_pca_ind(data_num_pca)#visualisation des résultats des individus
fviz_pca_ind(data_num_pca,
             geom.ind = "point", # Montre les points seulement (mais pas le "text")
             col.ind = data$authentique, # colorier par groups
             palette = "Set2",
             addEllipses = TRUE, 
             #ellipse.type = "confidence", # Ellipses de confiance, concentration si non spécifié
             legend.title = "Authticité",
             title = "Plans 1 & 2"
             )


fviz_pca_ind(data_num_pca,
             axes = c(1,3),
             geom.ind = "point", # Montre les points seulement (mais pas le "text")
             col.ind = data$authentique, # colorier par groups
             palette = "Set2",
             addEllipses = TRUE, 
             #ellipse.type = "confidence", # Ellipses de confiance, concentration si non spécifié
             legend.title = "Authticité",
             title = "Plans 1 & 3"
             )


fviz_pca_ind(data_num_pca,
             axes = c(2,3),
             geom.ind = "point", # Montre les points seulement (mais pas le "text")
             col.ind = data$authentique, # colorier par groups
             palette = "Set2",
             addEllipses = TRUE, 
             #ellipse.type = "confidence", # Ellipses de confiance, concentration si non spécifié
             legend.title = "Authticité",
             title = "Plans 2 & 3"
             )

fviz_contrib(data_num_pca, choice = "ind", addlabels = TRUE, ylim = c(0, 2), axes = 1:2)

#Ordonner la contribution des individus au regard des dimensions --> singulariser les billets les + / - contributifs
#fviz_pca_biplot(data_num_pca)# Création d’un biplot des individus et des variables.

fviz_pca_biplot (data_num_pca,
                col.ind = data$authentique, palette = "jco",
                addEllipses = TRUE, label = "var",
                col.var = "black", repel = TRUE,
                legend.title = "Authenticité",
                title = "Projection des individus et des variables sur les deux premiers plans factoriels")

fviz_cos2(data_num_pca, choice = "var", axes = 1)

fviz_cos2(data_num_pca, choice = "var", axes = 2)

fviz_cos2(data_num_pca, choice = "var", axes = 3)

fviz_cos2(data_num_pca, choice = "var", axes = 1:2)

fviz_cos2(data_num_pca, choice = "var", axes = 1:3)


fviz_cos2(data_num_pca, choice = "ind", axes = 1)

fviz_cos2(data_num_pca, choice = "ind", axes = 2)

fviz_cos2(data_num_pca, choice = "ind", axes = 3)

fviz_cos2(data_num_pca, choice = "ind", axes = 1:2)

fviz_cos2(data_num_pca, choice = "ind", axes = 1:3)

fviz_cos2(
  data_num_pca,
  choice = "ind",
  axes = 1:2,
  fill = "steelblue",
  color = "steelblue",
  sort.val =  "desc",
  top = 25)

Mission 2 - Appliquez un algorithme de classification (on peut en tester plusieurs)

#Calculer k-means avec k = 2

set.seed(666)
res.km <- kmeans(scale(data_ok[, -7]), 2, nstart = 25) #On normalise les données avec scale (sans la dernière colonne), on veut 2 clusters et on fait 25 itérations

# Clustering K-means montrant le groupe de chaque individu
res_km = res.km$cluster
fviz_cluster(res.km, data = data_ok,
             palette = "Set2", 
             geom = "point",
             ellipse.type = "norm", 
             ggtheme = theme_bw(), 
             title = "Classification k-means"
             )
Warning: argument title is deprecated; please use main instead.

#On cherche à savoir comment l'algo a classé les billets par rapport à la réalité 
data_ok$res_km = res_km
data_ok$resultat = data_ok$auth - data_ok$res_km
table(data_ok$resultat) #On a 69 faux billets bien classés, 92 vrais billets bien classés et seulement 9 faux négatifs ! +1 faux positif --> recoder l'une des deux variable et refaire la différence

-2 -1  0 
69  9 92 
data_kmeans = data_ok
data_ok = select(data_ok, subset = -c(8, 9))

ctable <- table(data_kmeans$res_km, data_kmeans$auth)
rownames(ctable) <- c("False", "True")
colnames(ctable) <- c("Cluster1", "Cluster2")
ctable
       
        Cluster1 Cluster2
  False        1       92
  True        69        8
#Comment visualiser graphiquement les deux partitions au même endroit ? --> crosstable entre les deux colonnes ? Matrice de confusion fourfoldplot

#On recode les variables : pour rappel dans la colonne auth : 1 = vrai billet et 0 = faux billet. Dans la colonne res_km, 1 = vrai billet, 2 = faux billet. Il faut donc tout mettre en 1 et 0
table_test_km = select(data_kmeans, subset = -c(9))
table_test_km$res_km[table_test_km$res_km>1] <- 0 

#Matrice de confusion via Caret :
kmeans = as.factor(table_test_km$res_km)
auth_kmeans = as.factor(table_test_km$auth)
test_confu = confusionMatrix(data=kmeans, reference = auth_kmeans)
Registered S3 methods overwritten by 'proxy':
  method               from    
  print.registry_field registry
  print.registry_entry registry
test_confu
Confusion Matrix and Statistics

          Reference
Prediction  0  1
         0 69  8
         1  1 92
                                          
               Accuracy : 0.9471          
                 95% CI : (0.9019, 0.9755)
    No Information Rate : 0.5882          
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.8923          
                                          
 Mcnemar's Test P-Value : 0.0455          
                                          
            Sensitivity : 0.9857          
            Specificity : 0.9200          
         Pos Pred Value : 0.8961          
         Neg Pred Value : 0.9892          
             Prevalence : 0.4118          
         Detection Rate : 0.4059          
   Detection Prevalence : 0.4529          
      Balanced Accuracy : 0.9529          
                                          
       'Positive' Class : 0               
                                          
#rm(ctable, table_test_km, kmeans, auth_kmeans, test_confu, res.km, res_km, data_kmeans)
data_hk = data_ok[, -7] #sélection des données
data_scale <- scale(data_hk) #Centrage réduction
hk_results = hclust(dist(data_scale)) #, method = "ward.D") #Application du clustering
clusters = cutree(hk_results, 2) #Sortie des clusters

dendr_color = fviz_dend(hk_results, k = 2,
                cex = 0.4,
                palette = "Set1",
                rect = TRUE, 
                rect_fill = TRUE,
                rect_border = "Set1", 
                labels_track_height = 0.4)
Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.
plot(dendr_color) #dendrogramme


ctable <- table(clusters, data_ok$auth)
rownames(ctable) <- c("False", "True")
colnames(ctable) <- c("Cluster1", "Cluster2")
ctable
        
clusters Cluster1 Cluster2
   False       69       24
   True         1       76
confu_hk = table(clusters, data_ok$auth)
confu_hk
        
clusters  0  1
       1 69 24
       2  1 76
#(https://www.rdocumentation.org/packages/FactoMineR/versions/2.4/topics/HCPC)

hcpc_clust = HCPC(data_num_pca, nb.clust=2, consol=TRUE, iter.max=25, min=5, 
  max=NULL, metric="euclidean", method="ward", graph=FALSE, proba=0.05, 
  cluster.CA="rows",kk=Inf,description=TRUE)

hcpc_clusters = hcpc_clust$data.clust$clust #on isole les clusters 

table_hcpc = table(hcpc_clusters, data_ok$auth)
table_hcpc
             
hcpc_clusters  0  1
            1  1 92
            2 69  8
#On obtient exactement les mêmes résultats qu'avec la méthode des kmeans !
data$authentique = factor(data$authentique, levels = c('True', 'False'), labels = c('vrai_billet', 'faux_billet')) #

# on créé les 4 labels :
classement_km = factor(paste(kmeans, data$authentique, sep = ' - '))
classement_hk = factor(paste(clusters, data$authentique, sep = ' - '))
classement_hcpc = factor(paste(hcpc_clusters, data$authentique, sep = ' - '))


fig_km = fviz_pca_ind(data_num_pca, 
             geom=c('point'),
             pointshape = 19,
             habillage = classement_km,
             palette = c('#B20000',  # 1 - faux billet
                         '#B26000',  # 1 - vrai billet 
                         '#00B2A0',  # 2 - faux billet
                         '#00B233'), # 2 - vrai billet
             alpha.ind="cos2",
             ellipse.type = "norm", 
             mean.point = FALSE,
             legend.title = "Légende",
             title = "Visualisation k-means"
)

fig_hk = fviz_pca_ind(data_num_pca, 
             geom=c('point'),
             pointshape = 19,
             habillage = classement_hk,
             palette = c('#B20000',  # 1 - faux billet
                         '#B26000',  # 1 - vrai billet 
                         '#00B2A0',  # 2 - faux billet
                         '#00B233'), # 2 - vrai billet
             alpha.ind="cos2",
             ellipse.type = "norm", 
             mean.point = FALSE,
             legend.title = "Légende",
             title = "Visualisation class. hiérarchique"
)

fig_hcpc = fviz_pca_ind(data_num_pca, 
             geom=c('point'),
             pointshape = 19,
             habillage = classement_hcpc,
             palette = c('#B20000',  # 1 - faux billet
                         '#B26000',  # 1 - vrai billet 
                         '#00B2A0',  # 2 - faux billet
                         '#00B233'), # 2 - vrai billet
             alpha.ind="cos2",
             ellipse.type = "norm", 
             mean.point = FALSE,
             legend.title = "Légende",
             title = "Visualisation HCPC"
)

fig_km

fig_hk

fig_hcpc

rm(data_hk, data_kmeans, data_num_pca_ind, data_num_pca_var, data_scale, dendr_color, eig_val, fig_hcpc, fig_hk, fig_km, hcpc_clust, res.km, table_test_km, test_confu, var, auth_kmeans, classement_hcpc, classement_hk, classement_km, clusters, confu_hk, ctable, hcpc_clusters, kmeans, res_km, table_hcpc, hk_results, data_test)

rm(data, data_num, data_num_pca, data_ok)

#Mission 3 - Modélisez les données à l’aide d’une régression logistique

3.2 Echantillonnage & fréquences

print(prop.table(table(data$is_genuine)))

    False      True 
0.4117647 0.5882353 
set.seed(100)
trainIndex <- createDataPartition(data$is_genuine,p=0.8,list=F)
print(length(trainIndex))
[1] 136
#Pour l'ensemble d'apprentissage
dataTrain <- data[trainIndex,]
print(dim(dataTrain))
[1] 136   7
#Pour l'échantillon test, via l'indiçage négatif
dataTest <- data[-trainIndex,]
print(dim(dataTest))
[1] 34  7
#fréquences absolues des classes - éch. d'apprentissage
print(table(dataTrain$is_genuine))

False  True 
   56    80 
#fréquences relatives des classes dans l'éch. d'apprentissage
print(prop.table(table(dataTrain$is_genuine)))

    False      True 
0.4117647 0.5882353 
#fréquences absolues des classes - éch. d'test
print(table(dataTest$is_genuine))

False  True 
   14    20 
#distribution des classes dans l'éch. test
print(prop.table(table(dataTest$is_genuine)))

    False      True 
0.4117647 0.5882353 
# --> Les fréquences relatives sont conformes avec la distribution initiale 

3.3 Modélisation

#paramètre du processu d'apprentissage : on laisse tout par défaut
fitControl <- trainControl(method="none")
#apprentissage - régression logistique
m_lr <- train(is_genuine ~ ., data = dataTrain,method="glm",trControl=fitControl)
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
print(m_lr)
Generalized Linear Model 

136 samples
  6 predictor
  2 classes: 'False', 'True' 

No pre-processing
Resampling: None 
#modèle sous-jacent issu de train
#coefficients de la régression logistique
print(m_lr$finalModel)

Call:  NULL

Coefficients:
 (Intercept)      diagonal   height_left  height_right    margin_low     margin_up        length  
   -2355.791         3.726       -70.844        37.852       -84.981       -87.064        51.958  

Degrees of Freedom: 135 Total (i.e. Null);  129 Residual
Null Deviance:      184.3 
Residual Deviance: 1.052e-08    AIC: 14
#AIC du modèle à 14

3.4 Prédiction

pred <- predict(m_lr,newdata=dataTest)
#distribution des classes prédites
print(table(pred))
pred
False  True 
   13    21 
#On a 13 prédiction de faux billets et 21 de vrais billets, à vérifier par la suite

3.5 Matrice de confusion et indicateurs d’évaluation

mat <- confusionMatrix(data=pred,reference=as.factor(dataTest$is_genuine),positive="True")
print(mat)
Confusion Matrix and Statistics

          Reference
Prediction False True
     False    13    0
     True      1   20
                                          
               Accuracy : 0.9706          
                 95% CI : (0.8467, 0.9993)
    No Information Rate : 0.5882          
    P-Value [Acc > NIR] : 3.624e-07       
                                          
                  Kappa : 0.9386          
                                          
 Mcnemar's Test P-Value : 1               
                                          
            Sensitivity : 1.0000          
            Specificity : 0.9286          
         Pos Pred Value : 0.9524          
         Neg Pred Value : 1.0000          
             Prevalence : 0.5882          
         Detection Rate : 0.5882          
   Detection Prevalence : 0.6176          
      Balanced Accuracy : 0.9643          
                                          
       'Positive' Class : True            
                                          
#Taux de succès (accuracy) à 97% ! L'intervalle de confiance à 95% est fourni mais l'échantillon étant faible, l'incertitude persiste. 
#On constate un faux positif.
#La sensibilité à la classe positive (True) s'établit à 100% (20/(20+0) !
print(mat$byClass)
         Sensitivity          Specificity       Pos Pred Value       Neg Pred Value            Precision               Recall 
           1.0000000            0.9285714            0.9523810            1.0000000            0.9523810            1.0000000 
                  F1           Prevalence       Detection Rate Detection Prevalence    Balanced Accuracy 
           0.9756098            0.5882353            0.5882353            0.6176471            0.9642857 
# Précision sur l'échantillon de test : 95% !

3.6 Evaluation du modèle

3.6.1 Courbe LIFT

#score des individus positifs
score <- predict(m_lr,dataTest,type="prob")[,"True"]
#print(quantile(score))

#On crée un data frame regroupant les classes observées et les scores
liftdata <- data.frame(classe=as.factor(dataTest$is_genuine))
liftdata$score <- score

#objet lift
lift_obj <- lift(classe ~ score, data=liftdata, class="True")
print(lift_obj) #La fonction print() indique seulement la proportion des observations positives (is_genuine = True).

Call:
lift.formula(x = classe ~ score, data = liftdata, class = "True")

Models: score 
Event: True (58.8%)
#affichage de la courbe lift
plot(lift_obj)

3.6.2 Coube ROC

#Score
score <- predict(m_lr,dataTest,type="prob")[,"True"]
#objet roc
roc_obj <- roc(dataTest$is_genuine=="True",score)
Setting levels: control = FALSE, case = TRUE
Setting direction: controls < cases
#plot de l'objet roc
plot(1-roc_obj$specificities, roc_obj$sensitivities, type="l")
abline(0,1)


#Peut-être un problème au niveau de l'encodage true/false --> assayer avec des 0/1 pour voir
print(roc_obj$auc)
Area under the curve: 1

3.7 Ré-échantillonnage

#évaluation par rééchantillonnage : validation croisée avec 10 blocs, 
fitControl <- trainControl(method="cv",number=10) #10 blocs 
m_lr <- train(is_genuine ~ ., data = dataTrain,method="glm",trControl=fitControl)
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
print(m_lr)
Generalized Linear Model 

136 samples
  6 predictor
  2 classes: 'False', 'True' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 122, 123, 122, 122, 122, 122, ... 
Resampling results:

  Accuracy  Kappa   
  0.978022  0.955158
print(m_lr$resample)

3.8 - Selection des variables

3.8.1 - Importance des variables

#importance des variables - intrinsèque au modèle
print(varImp(m_lr))
glm variable importance
#Variable la plus influente : longueur, puis marges, puis hauteurs, puis diagonale

3.8.2 - Méthode intégrée de sélection

m_lrs <- train(is_genuine ~ ., data = dataTrain, method="glmStepAIC",
trControl=trainControl("none"))
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Start:  AIC=14
.outcome ~ diagonal + height_left + height_right + margin_low + 
    margin_up + length
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
               Df Deviance    AIC
- diagonal      1    0.000 12.000
- height_right  1    0.000 12.000
- height_left   1    0.000 12.000
- margin_up     1    0.000 12.000
- length        1    0.000 12.000
<none>               0.000 14.000
- margin_low    1   21.779 33.779
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred

Step:  AIC=12
.outcome ~ height_left + height_right + margin_low + margin_up + 
    length
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
               Df Deviance    AIC
- height_right  1    0.000 10.000
- height_left   1    0.000 10.000
- margin_up     1    0.000 10.000
- length        1    0.000 10.000
<none>               0.000 12.000
- margin_low    1   30.348 40.347
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred

Step:  AIC=10
.outcome ~ height_left + margin_low + margin_up + length
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
              Df Deviance    AIC
- height_left  1    0.000  8.000
- margin_up    1    0.000  8.000
- length       1    0.000  8.000
<none>              0.000 10.000
- margin_low   1   39.357 47.357
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred

Step:  AIC=8
.outcome ~ margin_low + margin_up + length
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
             Df Deviance    AIC
- margin_up   1    0.000  6.000
<none>             0.000  8.000
- length      1    6.654 12.654
- margin_low  1   47.829 53.829
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred

Step:  AIC=6
.outcome ~ margin_low + length

             Df Deviance    AIC
<none>             0.000  6.000
- margin_low  1   54.882 58.882
- length      1   62.316 66.316
#Modèle final obtenu :
print(m_lrs$finalModel)

Call:  NULL

Coefficients:
(Intercept)   margin_low       length  
   -21884.2       -318.8        207.9  

Degrees of Freedom: 135 Total (i.e. Null);  133 Residual
Null Deviance:      184.3 
Residual Deviance: 8.055e-08    AIC: 6
#application sur les données test & mesure des performances
print(confusionMatrix(data=predict(m_lrs,newdata =
dataTest),reference=as.factor(dataTest$is_genuine),positive="True"))
Confusion Matrix and Statistics

          Reference
Prediction False True
     False    13    0
     True      1   20
                                          
               Accuracy : 0.9706          
                 95% CI : (0.8467, 0.9993)
    No Information Rate : 0.5882          
    P-Value [Acc > NIR] : 3.624e-07       
                                          
                  Kappa : 0.9386          
                                          
 Mcnemar's Test P-Value : 1               
                                          
            Sensitivity : 1.0000          
            Specificity : 0.9286          
         Pos Pred Value : 0.9524          
         Neg Pred Value : 1.0000          
             Prevalence : 0.5882          
         Detection Rate : 0.5882          
   Detection Prevalence : 0.6176          
      Balanced Accuracy : 0.9643          
                                          
       'Positive' Class : True            
                                          
LS0tDQp0aXRsZTogIlByb2pldCA2IC0gRMOpdGVjdGV6IGRlcyBmYXV4IGJpbGxldHMiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIExpYnJhaXJpZXMNCg0KYGBge3IgTGlicmFpcmllcyBwb3VyIGxlcyBhbmFseXNlcyB1bml2YXJpw6llcyAmIGJpdmFyacOpZXMsIGluY2x1ZGU9RkFMU0V9DQpsaWJyYXJ5KGZ1bk1vZGVsaW5nKSANCmxpYnJhcnkodGlkeXZlcnNlKSANCmxpYnJhcnkoSG1pc2MpDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoR0dhbGx5KQ0KbGlicmFyeShnZ3B1YnIpDQpsaWJyYXJ5KGdnY29ycnBsb3QpDQpsaWJyYXJ5KGNvcnJwbG90KQ0KbGlicmFyeShoZWF0bWFwbHkpDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmxpYnJhcnkoY29ycnBsb3QpDQpgYGANCg0KIyBNaXNlIGVuIHBsYWNlIGR1IGRhdGFzZXQNCg0KYGBge3IgQ2hhcmdlbWVudCBkZXMgZG9ubsOpZXMgJiBub20gZGVzIGNvbG9ubmVzfQ0KZGF0YSA9IHJlYWQuY3N2KGZpbGUgPSAibm90ZXMuY3N2IikNCm5hbWVzKGRhdGEpIDwtIGMoJ2F1dGhlbnRpcXVlJywgJ2RpYWdvbmFsZScsICdoYXV0ZXVyX2cnLCAnaGF1dGV1cl9kJywgJ21hcmdlX2JhcycsICdtYXJnZV9oYXV0JywgJ2xvbmd1ZXVyJykNCmhlYWQoZGF0YSwgMSkNCmBgYA0KDQpgYGB7ciBUcmFuc2Zvcm1hdGlvbiBkZSBsYSB2YXJpYWJsZSBhdXRoZW50aXF1ZSBlbiB2YXJpYWJsZSBiaW5haXJlfQ0KZGF0YV90ZXN0ID0gZGF0YQ0KZGF0YV90ZXN0JGF1dGggPC0gaWZlbHNlIChkYXRhX3Rlc3QkYXV0aGVudGlxdWUgPT0gIlRydWUiLCAxLCAwKQ0KZGF0YV9vayA9IHNlbGVjdChkYXRhX3Rlc3QsIHN1YnNldCA9IC1jKDEpKQ0Kcm0oZGF0YV90ZXN0KQ0KYGBgDQoNCg0KI01pc3Npb24gMCAtIEFmaW4gZCdpbnRyb2R1aXJlIHZvdHJlIGFuYWx5c2UsIGVmZmVjdHVleiB1bmUgYnLDqHZlIGRlc2NyaXB0aW9uIGRlcyBkb25uw6llcyAoYW5hbHlzZXMgdW5pdmFyacOpZXMgKGRlc2NyaXB0aW9ucywgZGVuc2l0w6ksIGhpc3RvZ3JhbW1lLCBib3hwbG90KSBldCBiaXZhcmnDqWVzKS4NCg0KYGBge3IgQmFzaWMgZWRhfQ0KI0ZvbmN0aW9uIHBvdXIgb2JzZXJ2ZXIgcmFwaWRlbWVudCBsZXMgZG9ubsOpZXMsIGxhIGRpc3RyaWJ1dGlvbiBkZXMgdmFyaWFibGVzIHF1YWxpIGV0IGxhIGRpc3RyaWJ1dGlvbiBkZXMgdmFyaWFibGVzIHF1YW50aQ0KDQpkYXRhX3Rlc3QgPSBkYXRhDQpkYXRhX3Rlc3QkYXV0aGVudGlxdWUgPSBmYWN0b3IoZGF0YV90ZXN0JGF1dGhlbnRpcXVlLCBsZXZlbHMgPSBjKCdUcnVlJywgJ0ZhbHNlJyksIGxhYmVscyA9IGMoJ3ZyYWlfYmlsbGV0JywgJ2ZhdXhfYmlsbGV0JykpICNPbiB2w6lyaWZpZSBsZXMgbml2ZWF1eCBkZSBsYSB2YXJpYWJsZSBxdWFsaSBkYW5zIGxlIGRhdGFmcmFtZSBkJ29yaWdpbmUsIGV0IG9uIHJlbGFiZWwNCmJhc2ljX2VkYSA8LSBmdW5jdGlvbihkYXRhX3Rlc3QpDQp7DQogIGdsaW1wc2UoZGF0YV90ZXN0KQ0KICBwcmludChzdGF0dXMoZGF0YV90ZXN0KSkNCiAgZnJlcShkYXRhX3Rlc3QpIA0KICBwcmludChwcm9maWxpbmdfbnVtKGRhdGFfdGVzdCkpDQogIHBsb3RfbnVtKGRhdGFfdGVzdCkNCiAgZGVzY3JpYmUoZGF0YV90ZXN0KQ0KfQ0KDQpiYXNpY19lZGEoZGF0YV90ZXN0KQ0KYGBgDQpgYGB7ciBJbnRlcnByw6l0YXRpb24gYmFzaWNfZWRhfQ0KI09uIGEgNyB2YXJpYWJsZSA6IDEgcXVhbGl0YXRpdmUgKCJhdXRoZW50aXF1ZSIsIHF1aSBwcmVuZHMgcG91ciB2YWxldXIgMCBzaSBsZSBiaWxsZXQgZXN0IGZhdXggZXQgMSBzaW5vbiksIGV0IDYgcXVhbGl0YXRpdmVzLCBxdWkgbWVzdXJlbnQgZGlmZsOpcmVudHMgYXNwZWN0cyBkdSBiaWxsZXQsIGF2ZWMgcG91ciB1bml0w6kgbGUgbWlsbGltw6h0cmUsIGV0IDIgZMOpY2ltYWxlcyBhcHLDqHMgbGEgdmlyZ3VsZS4gDQojIExlcyDDqWNhcnRzLXR5cGVzIGRlcyB2YXJpYWJsZXMgcXVhbnRpdGF0aXZlcyBzb250IHRyw6hzIGZhaWJsZXMgKC0xbW0pLg0KI1Byw6hzIGRlIDU5JSBkZXMgYmlsbGV0cyBzb250IHbDqXJpdGFibGVzICgxMDAgYmlsbGV0cykgY29udHJlIDQxJSBmYXV4ICg3MCBiaWxsZXRzKS4NCmBgYA0KDQoNCmBgYHtyIEJveHBsb3RzICYgZGVuc2l0w6l9DQojUG91ciB1biBib3hwbG90IGV0IGRlbnNpdMOpIGRlIHRvdXRlcyBsZXMgdmFyaWFibGVzIHF1YW50aSwgZW4gZm9uY3Rpb24gZGUgbGEgdmFyaWFibGUgcXVhbGkNCg0KZGYgPSBkYXRhDQpmaWcxIDwtIGRmICU+JSBwbG90X2x5KHggPSB+YXV0aGVudGlxdWUsIHkgPSB+ZGlhZ29uYWxlLCBzcGxpdCA9IH5hdXRoZW50aXF1ZSwgdHlwZSA9ICd2aW9saW4nLCBib3ggPSBsaXN0KHZpc2libGUgPSBUKSwgbWVhbmxpbmUgPSBsaXN0KHZpc2libGUgPSBUKSkgDQpmaWcxIDwtIGZpZzEgJT4lIGxheW91dCh4YXhpcyA9IGxpc3QodGl0bGUgPSAiQXV0aGVudGljaXTDqSIpLHlheGlzID0gbGlzdCh0aXRsZSA9ICJEaWFnb25hbGUiLHplcm9saW5lID0gRikpDQoNCmZpZzIgPC0gZGYgJT4lIHBsb3RfbHkoeCA9IH5hdXRoZW50aXF1ZSwgeSA9IH5oYXV0ZXVyX2csIHNwbGl0ID0gfmF1dGhlbnRpcXVlLCB0eXBlID0gJ3Zpb2xpbicsIGJveCA9IGxpc3QodmlzaWJsZSA9IFQpLCBtZWFubGluZSA9IGxpc3QodmlzaWJsZSA9IFQpKSANCmZpZzIgPC0gZmlnMiAlPiUgbGF5b3V0KHhheGlzID0gbGlzdCh0aXRsZSA9ICJBdXRoZW50aWNpdMOpIikseWF4aXMgPSBsaXN0KHRpdGxlID0gIkhhdXRldXIgZ2F1Y2hlIix6ZXJvbGluZSA9IEYpKQ0KDQpmaWczIDwtIGRmICU+JSBwbG90X2x5KHggPSB+YXV0aGVudGlxdWUsIHkgPSB+aGF1dGV1cl9kLCBzcGxpdCA9IH5hdXRoZW50aXF1ZSwgdHlwZSA9ICd2aW9saW4nLCBib3ggPSBsaXN0KHZpc2libGUgPSBUKSwgbWVhbmxpbmUgPSBsaXN0KHZpc2libGUgPSBUKSkgDQpmaWczIDwtIGZpZzMgJT4lIGxheW91dCh4YXhpcyA9IGxpc3QodGl0bGUgPSAiQXV0aGVudGljaXTDqSIpLHlheGlzID0gbGlzdCh0aXRsZSA9ICJIYXV0ZXVyIGRyb2l0ZSIsemVyb2xpbmUgPSBGKSkNCg0KZmlnNCA8LSBkZiAlPiUgcGxvdF9seSh4ID0gfmF1dGhlbnRpcXVlLCB5ID0gfm1hcmdlX2Jhcywgc3BsaXQgPSB+YXV0aGVudGlxdWUsIHR5cGUgPSAndmlvbGluJywgYm94ID0gbGlzdCh2aXNpYmxlID0gVCksIG1lYW5saW5lID0gbGlzdCh2aXNpYmxlID0gVCkpIA0KZmlnNCA8LSBmaWc0ICU+JSBsYXlvdXQoeGF4aXMgPSBsaXN0KHRpdGxlID0gIkF1dGhlbnRpY2l0w6kiKSx5YXhpcyA9IGxpc3QodGl0bGUgPSAiTWFyZ2UgYmFzc2UiLHplcm9saW5lID0gRikpDQoNCmZpZzUgPC0gZGYgJT4lIHBsb3RfbHkoeCA9IH5hdXRoZW50aXF1ZSwgeSA9IH5tYXJnZV9oYXV0LCBzcGxpdCA9IH5hdXRoZW50aXF1ZSwgdHlwZSA9ICd2aW9saW4nLCBib3ggPSBsaXN0KHZpc2libGUgPSBUKSwgbWVhbmxpbmUgPSBsaXN0KHZpc2libGUgPSBUKSkgDQpmaWc1IDwtIGZpZzUgJT4lIGxheW91dCh4YXhpcyA9IGxpc3QodGl0bGUgPSAiQXV0aGVudGljaXTDqSIpLHlheGlzID0gbGlzdCh0aXRsZSA9ICJNYXJnZSBoYXV0ZSIsemVyb2xpbmUgPSBGKSkNCg0KZmlnNiA8LSBkZiAlPiUgcGxvdF9seSh4ID0gfmF1dGhlbnRpcXVlLCB5ID0gfmxvbmd1ZXVyLCBzcGxpdCA9IH5hdXRoZW50aXF1ZSwgdHlwZSA9ICd2aW9saW4nLCBib3ggPSBsaXN0KHZpc2libGUgPSBUKSwgbWVhbmxpbmUgPSBsaXN0KHZpc2libGUgPSBUKSkgDQpmaWc2IDwtIGZpZzYgJT4lIGxheW91dCh4YXhpcyA9IGxpc3QodGl0bGUgPSAiQXV0aGVudGljaXTDqSIpLHlheGlzID0gbGlzdCh0aXRsZSA9ICJMb25ndWV1ciIsemVyb2xpbmUgPSBGKSkNCg0KZmlnMQ0KZmlnMg0KZmlnMw0KZmlnNA0KZmlnNQ0KZmlnNg0KYGBgDQoNCg0KYGBge3IgSW50ZXJwcsOpdGF0aW9uIGJveHBsb3QsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpNYXJnZSBiYXNzZSwgbWFyZ2UgaGF1dGUgZXQgbG9uZ3VldXIgcHLDqXNlbnRlbnQgbGVzIMOpY2FydHMgbGVzIHBsdXMgaW1wb3J0YW50cyBlbnRyZSBsZXMgdnJhaXMgZXQgbGVzIGZhdXggYmlsbGV0cw0KYGBgDQoNCg0KYGBge3IgQ29ycsOpbG9ncmFtbWV9DQojQ29ycsOpbG9ncmFtbWUgKGFuYWx5c2UgMiBwYXIgMikgZGVzIHZhcmlhYmxlcyBxdWFudGkgZW4gZm9uY3Rpb24gZGUgbGEgdmFyaWFibGUgcXVhbGksIHBsdXMgbml2ZWF1IGRlIGNvcnLDqWxhdGlvbiBlbiBnw6luw6lyYWwgZXQgZW4gZm9uY3Rpb24gZGUgbGEgdmFyaWFibGUgcXVhbGksIHBsdXMgc2lnbmlmaWNhdGl2aXTDqSBkdSB0YXV4IGRlIGNvcnLDqWxhdGlvbg0KDQpwIDwtIGdncGFpcnMoZGF0YSwgDQogICAgICAgICAgICAgY29sdW1ucyA9IDE6NywgDQogICAgICAgICAgICAgZ2dwbG90Mjo6YWVzKGNvbG91cj1hdXRoZW50aXF1ZSksIA0KICAgICAgICAgICAgIHRpdGxlPSJDb3Jyw6lsb2dyYW1lIikgDQpwDQpgYGANCg0KDQpgYGB7ciBIZWF0bWFwICYgbWF0cmljZSBkZXMgY29ycsOpbGF0aW9uc30NCmRhdGFfbnVtID0gc2VsZWN0KGRhdGEsIHN1YnNldCA9IC1jKDEpKQ0KDQojTWF0cmljZSBkZXMgY29ycsOpbGF0aW9ucw0KIyBDYWxjdWwgZGUgbGEgbWF0cmljZQ0KY29yciA8LSByb3VuZChjb3IoZGF0YV9udW0pLCAxKQ0KDQojIENhbGN1bCBkZSBsYSBtYXRyaWNlIGRlIHAtdmFsdWVzIGRlIGNvcnLDqWxhdGlvbg0KcC5tYXQgPC0gY29yX3BtYXQoZGF0YV9udW0pDQoNCiMgVmlzdWFsaXNlciBsZSB0cmlhbmdsZSBpbmbDqXJpZXVyIGRlIGxhIG1hdHJpY2UgZGUgY29ycsOpbGF0aW9uICYgQmFycmVyIGxlcyBjb2VmZmljaWVudHMgbm9uIHNpZ25pZmljYXRpZnMNCmNvcnIucGxvdCA8LSBnZ2NvcnJwbG90KA0KICBjb3JyLCBoYy5vcmRlciA9IFRSVUUsIHR5cGUgPSAibG93ZXIiLCBvdXRsaW5lLmNvbCA9ICJ3aGl0ZSIsDQogIHAubWF0ID0gcC5tYXQpDQojY29yci5wbG90DQpnZ3Bsb3RseShjb3JyLnBsb3QpDQoNCiNNYXRyaWNlIGRlIGNvcnLDqWxhdGlvbiBpbnZlcnPDqWUNCmNvcnJwbG90KGNvcnIsIHR5cGU9InVwcGVyIiwgb3JkZXI9ImhjbHVzdCIsIHRsLmNvbD0iYmxhY2siLCB0bC5zcnQ9NDUpDQoNCiMgQ2FsY3VsZXIgbGVzIGNvZWZmaWNpZW50cyBkZSBjb3Jyw6lsYXRpb24NCmNvci5jb2VmIDwtIGNvcihkYXRhX251bSkNCiMgQ2FsY3VsZXIgbGVzIHAtdmFsdWVzIGRlIGNvcnLDqWxhdGlvbg0KY29yLnRlc3QucCA8LSBmdW5jdGlvbih4KXsNCiAgICBGVU4gPC0gZnVuY3Rpb24oeCwgeSkgY29yLnRlc3QoeCwgeSlbWyJwLnZhbHVlIl1dDQogICAgeiA8LSBvdXRlcigNCiAgICAgIGNvbG5hbWVzKHgpLCANCiAgICAgIGNvbG5hbWVzKHgpLCANCiAgICAgIFZlY3Rvcml6ZShmdW5jdGlvbihpLGopIEZVTih4WyxpXSwgeFssal0pKQ0KICAgICkNCiAgICBkaW1uYW1lcyh6KSA8LSBsaXN0KGNvbG5hbWVzKHgpLCBjb2xuYW1lcyh4KSkNCiAgICB6DQp9DQoNCnAgPC0gY29yLnRlc3QucChkYXRhX251bSkNCg0KIyBDcsOpZXIgbGEgSGVhdG1hcA0KaGVhdG1hcGx5X2NvcigNCiAgY29yLmNvZWYsDQogIGtfY29sID0gMiwgDQogIGtfcm93ID0gMiwNCiAgbm9kZV90eXBlID0gInNjYXR0ZXIiLA0KICBwb2ludF9zaXplX21hdCA9IC1sb2cxMChwKSwgDQogIHBvaW50X3NpemVfbmFtZSA9ICItbG9nMTAocC12YWx1ZSkiLCAjb24gbG9nIHBvdXIgcmFtZW5lciBsZXMgY2hpZmZyZXMgZW50cmUgMCBldCAxIHBvdXIgZmFjaWxpdGVyIGwnaW50ZXJwcsOpdGF0aW9uICYgbGVzIG9yZHJlcyBkZSBncmFuZGV1ciANCiAgbGFiZWxfbmFtZXMgPSBjKCJ4IiwgInkiLCAiQ29ycmVsYXRpb24iKQ0KKQ0KYGBgDQoNCmBgYHtyIEludGVycHLDqXRhdGlvbiBjb3Jyw6lsYXRpb25zfQ0KI0NvcnLDqWxhdGlvbiBuw6lnYXRpdmUgZW50cmUgbGEgbG9uZ3VldXIgZXQgdG91dGVzIGxlcyBhdXRyZXMgdmFyaWFibGVzIFNBVUYgbGEgZGlhZ29uYWxlLCBxdWkgZXN0IHN0YXRpc3RpcXVlbWVudCBzaWduaWZpY2F0aXZlDQojQ29ycsOpbGF0aW9uIHBvc2l0aXZlIGltcG9ydGFudGUgZW50cmUgbGEgaGF1dGV1ciBnYXVjaGUgZXQgbGEgaGF1dGV1ciBkcm9pdGUNCmBgYA0KDQoNCmBgYHtyIE5ldHRveWFnZSAxfQ0Kcm0oY29yLmNvZWYsIGNvcnIsIGNvcnIucGxvdCwgZGYsIGZpZzEsIGZpZzIsIGZpZzMsIGZpZzQsIGZpZzUsIGZpZzYsIHAsIHAubWF0KQ0Kcm0oY29yLnRlc3QucCwgYmFzaWNfZWRhKQ0KYGBgDQoNCiMgTWlzc2lvbiAxIC0gVm91cyByw6lhbGlzZXJleiB1bmUgYW5hbHlzZSBlbiBjb21wb3NhbnRlcyBwcmluY2lwYWxlcyBkZSBsJ8OpY2hhbnRpbGxvbg0KDQpgYGB7ciBMaWJyYWlyaWVzIEFDUCwgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkoIkZhY3RvTWluZVIiKQ0KbGlicmFyeSgiZmFjdG9leHRyYSIpDQpsaWJyYXJ5KCJGYWN0b3NoaW55IikNCmxpYnJhcnkoImNhcmV0IikNCmBgYA0KDQpgYGB7ciBDZW50cm9pZGVzICYgaW5kaXZpZHVzIG1veWVuc30NCiNDYWxjdWwgZGVzIGluZGl2aWR1cyBtb3llbnMgOiBtb3llbm5lIGRlcyB2YXJpYWJsZXMgcGFyIGNhdMOpZ29yaWVzIDogVHJ1ZSAoMSkgLyBGYWxzZSAoMCkNCmluZGl2X21veSA9IGFnZ3JlZ2F0ZShkYXRhX251bSwgbGlzdChkYXRhJGF1dGgpLCBtZWFuKQ0KcHJpbnQoaW5kaXZfbW95KQ0KaW5kaXZfbW95ID0gc2VsZWN0KGluZGl2X21veSwgc3Vic2V0ID0gLWMoMSkpDQoNCiNDYWxjdWwgZW4gYW1vbnQgZGVzIGNlbnRyb2lkZXMNCmNlbnRyb2lkZXMgPSBzY2FsZShkYXRhX29rWywtN10sIGNlbnRlciA9IFRSVUUsIHNjYWxlID0gVFJVRSkNCmNlbnRyb2lkZXMgPSBhZ2dyZWdhdGUoY2VudHJvaWRlcywgbGlzdChkYXRhJGF1dGgpLCBtZWFuKQ0KcHJpbnQoY2VudHJvaWRlcykNCg0Kcm0oY2VudHJvaWRlcywgaW5kaXZfbW95KQ0KYGBgDQoNCg0KYGBge3IgQUNQLCB2YXJpYWJsZXMgJiBpbmRpdmlkdXN9DQojQUNQDQojZGF0YV9udW1fcXVhbnQgPSBzZWxlY3QoZGF0YV9udW0sIHN1YnNldCA9IC1jKDcpKQ0KZGF0YV9udW1fcGNhID0gUENBKGRhdGFfbnVtLCBzY2FsZS51bml0ID0gVFJVRSwgbmNwID0gNywgZ3JhcGggPSBGQUxTRSkNCmRhdGFfbnVtX3BjYV92YXIgPSBnZXRfcGNhX3ZhcihkYXRhX251bV9wY2EpDQpkYXRhX251bV9wY2FfaW5kID0gZ2V0X3BjYV9pbmQoZGF0YV9udW1fcGNhKQ0KYGBgDQoNCmBgYHtyIEVib3VsaXMgZGVzIHZhbGV1cnMgcHJvcHJlc30NCmVpZ192YWwgPSBnZXRfZWlnZW52YWx1ZShkYXRhX251bV9wY2EpIA0KI0V4dHJhY3Rpb24gZGVzIHZhbGV1cnMgcHJvcHJlcyAvIHZhcmlhbmNlcyBkZXMgY29tcG9zYW50ZXMgcHJpbmNpcGFsZXMgLSBMYSBwcm9wb3J0aW9uIGRlIHZhcmlhbmNlIGV4cGxpcXXDqWUgcGFyIGNoYXF1ZSB2YWxldXIgcHJvcHJlIGVzdCBkb25uw6llIGRhbnMgbGEgZGV1eGnDqG1lIGNvbG9ubmUuIExlIHBvdXJjZW50YWdlIGN1bXVsw6kgZXhwbGlxdcOpIGVzdCBvYnRlbnUgZW4gYWpvdXRhbnQgbGVzIHByb3BvcnRpb25zIHN1Y2Nlc3NpdmVzIGRlIHZhcmlhbmNlcyBleHBsaXF1w6llcy4gTGVzIHZhbGV1cnMgcHJvcHJlcyBwZXV2ZW50IMOqdHJlIHV0aWxpc8OpZXMgcG91ciBkw6l0ZXJtaW5lciBsZSBub21icmUgZOKAmWF4ZXMgcHJpbmNpcGF1eCDDoCBjb25zZXJ2ZXIgYXByw6hzIGzigJlBQ1AgKEthaXNlciAxOTYxKQ0KZWlnX3ZhbA0KZnZpel9laWcoZGF0YV9udW1fcGNhLCBhZGRsYWJlbHMgPSBUUlVFLCB5bGltID0gYygwLCA3MCkpICNWaXN1YWxpc2F0aW9uIGRlcyB2YWxldXJzIHByb3ByZXMNCmBgYA0KDQpgYGB7ciBDZXJjbGUgZGVzIGNvcnLDqWxhdGlvbnMgZGVzIHZhcmlhYmxlc30NCmdldF9wY2FfdmFyKGRhdGFfbnVtX3BjYSkjRXh0cmFjdGlvbiBkZXMgcsOpc3VsdGF0cyBwb3VyIGxlcyBsZXMgdmFyaWFibGVzDQojZnZpel9wY2FfdmFyKGRhdGFfbnVtX3BjYSkjdmlzdWFsaXNhdGlvbiBkZXMgcsOpc3VsdGF0cyBkZXMgdmFyaWFibGVzDQojY29zMiA6IHF1YWxpdMOpIGRlIHJlcHLDqXNlbnRhdGlvbg0KDQp2YXIgPC0gZ2V0X3BjYV92YXIoZGF0YV9udW1fcGNhKQ0KY29ycnBsb3QodmFyJGNvczIsIGlzLmNvcnI9VFJVRSkNCg0KZnZpel9wY2FfdmFyKGRhdGFfbnVtX3BjYSwgDQogICAgICAgICAgICAgdGl0bGU9J0NlcmNsZSBkZSBjb3Jyw6lsYXRpb24nLA0KICAgICAgICAgICAgIGNvbC52YXIgPSAiY29zMiIsDQogICAgICAgICAgICAgZ3JhZGllbnQuY29scyA9IGMoIiMwMEFGQkIiLCAiI0U3QjgwMCIsICIjRkM0RTA3IiksDQogICAgICAgICAgICAgcmVwZWwgPSBUUlVFICMgw4l2aXRlIGxlIGNoZXZhdWNoZW1lbnQgZGUgdGV4dGUNCiAgICAgICAgICAgICApDQpmdml6X3BjYV92YXIoZGF0YV9udW1fcGNhLA0KICAgICAgICAgICAgIHRpdGxlPSdDZXJjbGUgZGUgY29udHJpYnV0aW9uJywNCiAgICAgICAgICAgICBjb2wudmFyID0gImNvbnRyaWIiLA0KICAgICAgICAgICAgIGdyYWRpZW50LmNvbHMgPSBjKCIjMDBBRkJCIiwgIiNFN0I4MDAiLCAiI0ZDNEUwNyIpDQogICAgICAgICAgICAgKQ0KDQpgYGANCg0KYGBge3IgaW50ZXJwcsOpdGF0aW9uIGNlcmNsZSBkZXMgY29ycsOpbGF0aW9ucywgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCkxhIGRpYWdvbmFsZSBlc3QgcG9zaXRpdmVtZW50IGNvcnLDqWzDqWUgw6AgRGltMiwgdG91dGVzIGxlcyBhdXRyZXMgdmFyaWFibGVzIHNvbnQgcG9zaXRpdmVtZW50IGNvcnLDqWzDqWVzIMOgIERpbTEgc2F1ZiBsYSBsb25ndWV1ciwgbsOpZ2F0aXZlbWVudCBjb3Jyw6lsw6llIMOgIERpbTEuDQpUb3V0ZXMgbGVzIHZhcmlhYmxlcyBzb250IHBsdXTDtHQgYmllbiByZXByw6lzZW50w6llcyBzdXIgUEMxIHNhdWYgbGEgZGlhZ29uYWxlIChQQzIpIGV0IGxhIG1hcmdlIGhhdXRlIChQQzMpLiBPbiB2YSBkb25jIHNhbnMgZG91dGUgcmVnYXJkZXIgbGVzIDMgcHJlbWllcnMgcGxhbnMgZmFjdG9yaWVscw0KYGBgDQoNCg0KYGBge3IgUmVwcsOpc2VudGF0aW9uIGRlcyBpbmRpdmlkdXMgZGFucyBsZSBwbGFuIGZhY3RvcmllbH0NCmdldF9wY2FfaW5kKGRhdGFfbnVtX3BjYSkjRXh0cmFjdGlvbiBkZXMgcsOpc3VsdGF0cyBwb3VyIGxlcyBpbmRpdmlkdXMNCiNmdml6X3BjYV9pbmQoZGF0YV9udW1fcGNhKSN2aXN1YWxpc2F0aW9uIGRlcyByw6lzdWx0YXRzIGRlcyBpbmRpdmlkdXMNCmZ2aXpfcGNhX2luZChkYXRhX251bV9wY2EsDQogICAgICAgICAgICAgZ2VvbS5pbmQgPSAicG9pbnQiLCAjIE1vbnRyZSBsZXMgcG9pbnRzIHNldWxlbWVudCAobWFpcyBwYXMgbGUgInRleHQiKQ0KICAgICAgICAgICAgIGNvbC5pbmQgPSBkYXRhJGF1dGhlbnRpcXVlLCAjIGNvbG9yaWVyIHBhciBncm91cHMNCiAgICAgICAgICAgICBwYWxldHRlID0gIlNldDIiLA0KICAgICAgICAgICAgIGFkZEVsbGlwc2VzID0gVFJVRSwgDQogICAgICAgICAgICAgI2VsbGlwc2UudHlwZSA9ICJjb25maWRlbmNlIiwgIyBFbGxpcHNlcyBkZSBjb25maWFuY2UsIGNvbmNlbnRyYXRpb24gc2kgbm9uIHNww6ljaWZpw6kNCiAgICAgICAgICAgICBsZWdlbmQudGl0bGUgPSAiQXV0aHRpY2l0w6kiLA0KICAgICAgICAgICAgIHRpdGxlID0gIlBsYW5zIDEgJiAyIg0KICAgICAgICAgICAgICkNCg0KZnZpel9wY2FfaW5kKGRhdGFfbnVtX3BjYSwNCiAgICAgICAgICAgICBheGVzID0gYygxLDMpLA0KICAgICAgICAgICAgIGdlb20uaW5kID0gInBvaW50IiwgIyBNb250cmUgbGVzIHBvaW50cyBzZXVsZW1lbnQgKG1haXMgcGFzIGxlICJ0ZXh0IikNCiAgICAgICAgICAgICBjb2wuaW5kID0gZGF0YSRhdXRoZW50aXF1ZSwgIyBjb2xvcmllciBwYXIgZ3JvdXBzDQogICAgICAgICAgICAgcGFsZXR0ZSA9ICJTZXQyIiwNCiAgICAgICAgICAgICBhZGRFbGxpcHNlcyA9IFRSVUUsIA0KICAgICAgICAgICAgICNlbGxpcHNlLnR5cGUgPSAiY29uZmlkZW5jZSIsICMgRWxsaXBzZXMgZGUgY29uZmlhbmNlLCBjb25jZW50cmF0aW9uIHNpIG5vbiBzcMOpY2lmacOpDQogICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gIkF1dGh0aWNpdMOpIiwNCiAgICAgICAgICAgICB0aXRsZSA9ICJQbGFucyAxICYgMyINCiAgICAgICAgICAgICApDQoNCmZ2aXpfcGNhX2luZChkYXRhX251bV9wY2EsDQogICAgICAgICAgICAgYXhlcyA9IGMoMiwzKSwNCiAgICAgICAgICAgICBnZW9tLmluZCA9ICJwb2ludCIsICMgTW9udHJlIGxlcyBwb2ludHMgc2V1bGVtZW50IChtYWlzIHBhcyBsZSAidGV4dCIpDQogICAgICAgICAgICAgY29sLmluZCA9IGRhdGEkYXV0aGVudGlxdWUsICMgY29sb3JpZXIgcGFyIGdyb3Vwcw0KICAgICAgICAgICAgIHBhbGV0dGUgPSAiU2V0MiIsDQogICAgICAgICAgICAgYWRkRWxsaXBzZXMgPSBUUlVFLCANCiAgICAgICAgICAgICAjZWxsaXBzZS50eXBlID0gImNvbmZpZGVuY2UiLCAjIEVsbGlwc2VzIGRlIGNvbmZpYW5jZSwgY29uY2VudHJhdGlvbiBzaSBub24gc3DDqWNpZmnDqQ0KICAgICAgICAgICAgIGxlZ2VuZC50aXRsZSA9ICJBdXRodGljaXTDqSIsDQogICAgICAgICAgICAgdGl0bGUgPSAiUGxhbnMgMiAmIDMiDQogICAgICAgICAgICAgKQ0KYGBgDQoNCmBgYHtyIEludGVycHLDqXRhdGlvbiByZXByw6lzZW50YXRpb24gZGVzIGluZGl2aWR1cyBkYW5zIGxlIHBsYW4gZmFjdG9yaWVsLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KU3VyIGxlcyBkZXV4IHByZW1pZXJzIHBsYW5zIGZhY3RvcmllbHMsIG9uIGNvdXZyZSArNjklIGRlIGwnaW5mb3JtYXRpb24uIFRyw6hzIHBldSBkZSBjaGV2YXVjaGVtZW50IGVudHJlIGxlcyBkZXV4IGdyb3VwZXMsIGMnZXN0IGRvbmMgdW5lIHJlcHLDqXNlbnRhdGlvbiBhIHByaW9yaSBwbHV0w7R0IGZpZMOobGUgLS0+IGxlcyBkZXV4IHByZW1pw6hyZXMgZGltZW5zaW9ucyBwZXJtZXR0ZW50IGRlIGRpc3Rpbmd1ZXIgZWZmaWNhY2VtZW50IHVuIHZyYWkgYmlsbGV0IGQndW4gZmF1eC4NCg0KU3VyIGxlcyBwbGFucyAxICYgMywgbGUgY2hldmF1dmVtZW50IGVzdCBwbHVzIGltcG9ydGFudCBldCBsYSBwYXJ0IGRlIGwnaW5mb3JtYXRpb24gcmVwcsOpc2VudMOpZSB0b21iZSDDoCArNjElLiBDZWzDoCByZXN0ZSBpbnTDqXJlc3NhbnQgbWFpcyBtb2lucyBxdWUgbGVzIGRldXggcHJlbWllcnMgcGxhbnMuDQoNClN1ciBsZXMgcGxhbnMgMiYzLCBvbiBuZSBjb3V2cmUgcGx1cyBxdWUgMjYlIGRlIGwnaW5mb3JtYXRpb24gZXQgbGUgY2hldmF1Y2hlbWVudCBlc3QgdHLDqHMgaW1wb3J0YW50IDogcGV1IHBlcnRpbmVudCBwb3VyIHJlcHLDqXNlbnRlciBsJ2F1dGhlbnRpY2l0w6kgZCd1biBiaWxsZXQNCmBgYA0KDQoNCmBgYHtyIENvbnRyaWJ1dGlvbiBkZXMgaW5kaXZpZHVzfQ0KZnZpel9jb250cmliKGRhdGFfbnVtX3BjYSwgY2hvaWNlID0gImluZCIsIGFkZGxhYmVscyA9IFRSVUUsIHlsaW0gPSBjKDAsIDIpLCBheGVzID0gMToyKQ0KI09yZG9ubmVyIGxhIGNvbnRyaWJ1dGlvbiBkZXMgaW5kaXZpZHVzIGF1IHJlZ2FyZCBkZXMgZGltZW5zaW9ucyAtLT4gc2luZ3VsYXJpc2VyIGxlcyBiaWxsZXRzIGxlcyArIC8gLSBjb250cmlidXRpZnMNCmBgYA0KDQoNCmBgYHtyIEJpcGxvdCBkZXMgaW5kaXZpZHVzICYgdmFyaWFibGVzfQ0KI2Z2aXpfcGNhX2JpcGxvdChkYXRhX251bV9wY2EpIyBDcsOpYXRpb24gZOKAmXVuIGJpcGxvdCBkZXMgaW5kaXZpZHVzIGV0IGRlcyB2YXJpYWJsZXMuDQoNCmZ2aXpfcGNhX2JpcGxvdCAoZGF0YV9udW1fcGNhLA0KICAgICAgICAgICAgICAgIGNvbC5pbmQgPSBkYXRhJGF1dGhlbnRpcXVlLCBwYWxldHRlID0gImpjbyIsDQogICAgICAgICAgICAgICAgYWRkRWxsaXBzZXMgPSBUUlVFLCBsYWJlbCA9ICJ2YXIiLA0KICAgICAgICAgICAgICAgIGNvbC52YXIgPSAiYmxhY2siLCByZXBlbCA9IFRSVUUsDQogICAgICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gIkF1dGhlbnRpY2l0w6kiLA0KICAgICAgICAgICAgICAgIHRpdGxlID0gIlByb2plY3Rpb24gZGVzIGluZGl2aWR1cyBldCBkZXMgdmFyaWFibGVzIHN1ciBsZXMgZGV1eCBwcmVtaWVycyBwbGFucyBmYWN0b3JpZWxzIikNCmBgYA0KDQpgYGB7ciBDb3MyIGRlcyBpbmRpdmlkdXMgJiB2YXJpYWJsZXMgc3VyIGxlcyAxLTItMyBwcmVtacOocmVzIGRpbWVuc2lvbnN9DQpmdml6X2NvczIoZGF0YV9udW1fcGNhLCBjaG9pY2UgPSAidmFyIiwgYXhlcyA9IDEpDQpmdml6X2NvczIoZGF0YV9udW1fcGNhLCBjaG9pY2UgPSAidmFyIiwgYXhlcyA9IDIpDQpmdml6X2NvczIoZGF0YV9udW1fcGNhLCBjaG9pY2UgPSAidmFyIiwgYXhlcyA9IDMpDQpmdml6X2NvczIoZGF0YV9udW1fcGNhLCBjaG9pY2UgPSAidmFyIiwgYXhlcyA9IDE6MikNCmZ2aXpfY29zMihkYXRhX251bV9wY2EsIGNob2ljZSA9ICJ2YXIiLCBheGVzID0gMTozKQ0KDQpmdml6X2NvczIoZGF0YV9udW1fcGNhLCBjaG9pY2UgPSAiaW5kIiwgYXhlcyA9IDEpDQpmdml6X2NvczIoZGF0YV9udW1fcGNhLCBjaG9pY2UgPSAiaW5kIiwgYXhlcyA9IDIpDQpmdml6X2NvczIoZGF0YV9udW1fcGNhLCBjaG9pY2UgPSAiaW5kIiwgYXhlcyA9IDMpDQpmdml6X2NvczIoZGF0YV9udW1fcGNhLCBjaG9pY2UgPSAiaW5kIiwgYXhlcyA9IDE6MikNCmZ2aXpfY29zMihkYXRhX251bV9wY2EsIGNob2ljZSA9ICJpbmQiLCBheGVzID0gMTozKQ0KYGBgDQoNCmBgYHtyIFRvcCAyNSBkZXMgaW5kaXZpZHVzIGxlcyBwbHVzIGRpc2NyaW1pbmFudCBzdXIgbGVzIGRpbWVuc2lvbnMgMS0yfQ0KZnZpel9jb3MyKA0KICBkYXRhX251bV9wY2EsDQogIGNob2ljZSA9ICJpbmQiLA0KICBheGVzID0gMToyLA0KICBmaWxsID0gInN0ZWVsYmx1ZSIsDQogIGNvbG9yID0gInN0ZWVsYmx1ZSIsDQogIHNvcnQudmFsID0gICJkZXNjIiwNCiAgdG9wID0gMjUpDQpgYGANCg0KDQpgYGB7ciBQQ0FTaGlueSwgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NClBDQXNoaW55KGRhdGEpDQojIE91IHJlc3NoaW55ID0gUENBc2hpbnkoZGF0YV9udW1fcGNhKSANCmBgYA0KDQpgYGB7ciBOZXR0b3lhZ2Ugb3B0aW9ubmVsLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0Kcm0oZGF0YV9udW1fcGNhLCBkYXRhX251bV9wY2FfaW5kLCBkYXRhX251bV9wY2FfdmFyLCBlaWdfdmFsLCB2YXIpDQpgYGANCg0KIyBNaXNzaW9uIDIgLSBBcHBsaXF1ZXogdW4gYWxnb3JpdGhtZSBkZSBjbGFzc2lmaWNhdGlvbiAob24gcGV1dCBlbiB0ZXN0ZXIgcGx1c2lldXJzKQ0KDQpgYGB7ciBDbGFzc2lmaWNhdGluIGstbWVhbnN9DQojQ2FsY3VsZXIgay1tZWFucyBhdmVjIGsgPSAyDQoNCnNldC5zZWVkKDY2NikNCnJlcy5rbSA8LSBrbWVhbnMoc2NhbGUoZGF0YV9va1ssIC03XSksIDIsIG5zdGFydCA9IDI1KSAjT24gbm9ybWFsaXNlIGxlcyBkb25uw6llcyBhdmVjIHNjYWxlIChzYW5zIGxhIGRlcm5pw6hyZSBjb2xvbm5lKSwgb24gdmV1dCAyIGNsdXN0ZXJzIGV0IG9uIGZhaXQgMjUgaXTDqXJhdGlvbnMNCg0KIyBDbHVzdGVyaW5nIEstbWVhbnMgbW9udHJhbnQgbGUgZ3JvdXBlIGRlIGNoYXF1ZSBpbmRpdmlkdQ0KcmVzX2ttID0gcmVzLmttJGNsdXN0ZXINCmZ2aXpfY2x1c3RlcihyZXMua20sIGRhdGEgPSBkYXRhX29rLA0KICAgICAgICAgICAgIHBhbGV0dGUgPSAiU2V0MiIsIA0KICAgICAgICAgICAgIGdlb20gPSAicG9pbnQiLA0KICAgICAgICAgICAgIGVsbGlwc2UudHlwZSA9ICJub3JtIiwgDQogICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX2J3KCksIA0KICAgICAgICAgICAgIHRpdGxlID0gIkNsYXNzaWZpY2F0aW9uIGstbWVhbnMiDQogICAgICAgICAgICAgKQ0KDQojT24gY2hlcmNoZSDDoCBzYXZvaXIgY29tbWVudCBsJ2FsZ28gYSBjbGFzc8OpIGxlcyBiaWxsZXRzIHBhciByYXBwb3J0IMOgIGxhIHLDqWFsaXTDqSANCmRhdGFfb2skcmVzX2ttID0gcmVzX2ttDQpkYXRhX29rJHJlc3VsdGF0ID0gZGF0YV9vayRhdXRoIC0gZGF0YV9vayRyZXNfa20NCnRhYmxlKGRhdGFfb2skcmVzdWx0YXQpICNPbiBhIDY5IGZhdXggYmlsbGV0cyBiaWVuIGNsYXNzw6lzLCA5MiB2cmFpcyBiaWxsZXRzIGJpZW4gY2xhc3PDqXMgZXQgc2V1bGVtZW50IDkgZmF1eCBuw6lnYXRpZnMgISArMSBmYXV4IHBvc2l0aWYgLS0+IHJlY29kZXIgbCd1bmUgZGVzIGRldXggdmFyaWFibGUgZXQgcmVmYWlyZSBsYSBkaWZmw6lyZW5jZQ0KZGF0YV9rbWVhbnMgPSBkYXRhX29rDQpkYXRhX29rID0gc2VsZWN0KGRhdGFfb2ssIHN1YnNldCA9IC1jKDgsIDkpKQ0KDQpjdGFibGUgPC0gdGFibGUoZGF0YV9rbWVhbnMkcmVzX2ttLCBkYXRhX2ttZWFucyRhdXRoKQ0Kcm93bmFtZXMoY3RhYmxlKSA8LSBjKCJGYWxzZSIsICJUcnVlIikNCmNvbG5hbWVzKGN0YWJsZSkgPC0gYygiQ2x1c3RlcjEiLCAiQ2x1c3RlcjIiKQ0KY3RhYmxlDQoNCiNDb21tZW50IHZpc3VhbGlzZXIgZ3JhcGhpcXVlbWVudCBsZXMgZGV1eCBwYXJ0aXRpb25zIGF1IG3Dqm1lIGVuZHJvaXQgPyAtLT4gY3Jvc3N0YWJsZSBlbnRyZSBsZXMgZGV1eCBjb2xvbm5lcyA/IE1hdHJpY2UgZGUgY29uZnVzaW9uIGZvdXJmb2xkcGxvdA0KDQojT24gcmVjb2RlIGxlcyB2YXJpYWJsZXMgOiBwb3VyIHJhcHBlbCBkYW5zIGxhIGNvbG9ubmUgYXV0aCA6IDEgPSB2cmFpIGJpbGxldCBldCAwID0gZmF1eCBiaWxsZXQuIERhbnMgbGEgY29sb25uZSByZXNfa20sIDEgPSB2cmFpIGJpbGxldCwgMiA9IGZhdXggYmlsbGV0LiBJbCBmYXV0IGRvbmMgdG91dCBtZXR0cmUgZW4gMSBldCAwDQp0YWJsZV90ZXN0X2ttID0gc2VsZWN0KGRhdGFfa21lYW5zLCBzdWJzZXQgPSAtYyg5KSkNCnRhYmxlX3Rlc3Rfa20kcmVzX2ttW3RhYmxlX3Rlc3Rfa20kcmVzX2ttPjFdIDwtIDAgDQoNCiNNYXRyaWNlIGRlIGNvbmZ1c2lvbiB2aWEgQ2FyZXQgOg0Ka21lYW5zID0gYXMuZmFjdG9yKHRhYmxlX3Rlc3Rfa20kcmVzX2ttKQ0KYXV0aF9rbWVhbnMgPSBhcy5mYWN0b3IodGFibGVfdGVzdF9rbSRhdXRoKQ0KdGVzdF9jb25mdSA9IGNvbmZ1c2lvbk1hdHJpeChkYXRhPWttZWFucywgcmVmZXJlbmNlID0gYXV0aF9rbWVhbnMpDQp0ZXN0X2NvbmZ1DQoNCiNybShjdGFibGUsIHRhYmxlX3Rlc3Rfa20sIGttZWFucywgYXV0aF9rbWVhbnMsIHRlc3RfY29uZnUsIHJlcy5rbSwgcmVzX2ttLCBkYXRhX2ttZWFucykNCmBgYA0KDQpgYGB7ciBDbGFzc2lmaWNhdGlvbiBhc2NlbmRhbnRlIGhpw6lyYXJjaGlxdWUgc2ltcGxpZmnDqWV9DQpkYXRhX2hrID0gZGF0YV9va1ssIC03XSAjc8OpbGVjdGlvbiBkZXMgZG9ubsOpZXMNCmRhdGFfc2NhbGUgPC0gc2NhbGUoZGF0YV9oaykgI0NlbnRyYWdlIHLDqWR1Y3Rpb24NCmhrX3Jlc3VsdHMgPSBoY2x1c3QoZGlzdChkYXRhX3NjYWxlKSkgIywgbWV0aG9kID0gIndhcmQuRCIpICNBcHBsaWNhdGlvbiBkdSBjbHVzdGVyaW5nDQpjbHVzdGVycyA9IGN1dHJlZShoa19yZXN1bHRzLCAyKSAjU29ydGllIGRlcyBjbHVzdGVycw0KDQpkZW5kcl9jb2xvciA9IGZ2aXpfZGVuZChoa19yZXN1bHRzLCBrID0gMiwNCiAgICAgICAgICAgICAgICBjZXggPSAwLjQsDQogICAgICAgICAgICAgICAgcGFsZXR0ZSA9ICJTZXQxIiwNCiAgICAgICAgICAgICAgICByZWN0ID0gVFJVRSwgDQogICAgICAgICAgICAgICAgcmVjdF9maWxsID0gVFJVRSwNCiAgICAgICAgICAgICAgICByZWN0X2JvcmRlciA9ICJTZXQxIiwgDQogICAgICAgICAgICAgICAgbGFiZWxzX3RyYWNrX2hlaWdodCA9IDAuNCkNCnBsb3QoZGVuZHJfY29sb3IpICNkZW5kcm9ncmFtbWUNCg0KY3RhYmxlIDwtIHRhYmxlKGNsdXN0ZXJzLCBkYXRhX29rJGF1dGgpDQpyb3duYW1lcyhjdGFibGUpIDwtIGMoIkZhbHNlIiwgIlRydWUiKQ0KY29sbmFtZXMoY3RhYmxlKSA8LSBjKCJDbHVzdGVyMSIsICJDbHVzdGVyMiIpDQpjdGFibGUNCg0KY29uZnVfaGsgPSB0YWJsZShjbHVzdGVycywgZGF0YV9vayRhdXRoKQ0KY29uZnVfaGsNCmBgYA0KDQpgYGB7ciBJbnRlcnByw6l0YXRpb24gY2xhc3NpZmljYXRpb24gaGnDqXJhcmNoaXF1ZSwgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCk9uIGEgdW5lIG1ham9yaXTDqSBkZSB2cmFpcyBiaWxsZXRzICgxIGVuIGxpZ25lKSBkYW5zIGxlIGNsdXN0ZXIgMg0KTGEgbWFqb3JpdMOpIGRlcyBmYXV4IGJpbGxldHMgKDAgZW4gbGlnbmUpIGVzdCBkYW5zIGxlIGNsdXN0ZXIgMQ0KT24gYSB0b3V0IGRlIG3Dqm1lIDI0IGJpbGxldHMgdnJhaXMgY2xhc3PDqXMgY29tbWUgZmF1eCAoZG9uYyBmYXV4IG7DqWdhdGlmcykNClVuIHNldWwgZmF1eCBwb3NpdGlmDQoNClBhciByYXBwb3J0IMOgIGxhIGNsYXNzaWZpY2F0aW9uIHByw6ljw6lkZW50ZSwgb24gcGVyZCBlbiAicG91dm9pciBkZSBwcsOpZGljdGlvbiIgZW4gY29tcGFyYW50IMOgIGxhIHLDqWFsaXTDqQ0KYGBgDQoNCmBgYHtyIENsYXNzaWZpY2F0aW9uIEhpw6lyYXJjaGlxdWUgc3VyIENvbXBvc2FudGVzIFByaW5jaXBhbGVzfQ0KIyhodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvRmFjdG9NaW5lUi92ZXJzaW9ucy8yLjQvdG9waWNzL0hDUEMpDQoNCmhjcGNfY2x1c3QgPSBIQ1BDKGRhdGFfbnVtX3BjYSwgbmIuY2x1c3Q9MiwgY29uc29sPVRSVUUsIGl0ZXIubWF4PTI1LCBtaW49NSwgDQogIG1heD1OVUxMLCBtZXRyaWM9ImV1Y2xpZGVhbiIsIG1ldGhvZD0id2FyZCIsIGdyYXBoPUZBTFNFLCBwcm9iYT0wLjA1LCANCiAgY2x1c3Rlci5DQT0icm93cyIsa2s9SW5mLGRlc2NyaXB0aW9uPVRSVUUpDQoNCmhjcGNfY2x1c3RlcnMgPSBoY3BjX2NsdXN0JGRhdGEuY2x1c3QkY2x1c3QgI29uIGlzb2xlIGxlcyBjbHVzdGVycyANCg0KdGFibGVfaGNwYyA9IHRhYmxlKGhjcGNfY2x1c3RlcnMsIGRhdGFfb2skYXV0aCkNCnRhYmxlX2hjcGMNCg0KI09uIG9idGllbnQgZXhhY3RlbWVudCBsZXMgbcOqbWVzIHLDqXN1bHRhdHMgcXUnYXZlYyBsYSBtw6l0aG9kZSBkZXMga21lYW5zICENCg0KYGBgDQoNCg0KYGBge3IgVmlzdWFsaXNhdGlvbiBkZXMgY2xhc3NpZmljYXRpb25zIGRhbnMgbGUgcHJlbWllciBwbGFuIGZhY3RvcmllbH0NCmRhdGEkYXV0aGVudGlxdWUgPSBmYWN0b3IoZGF0YSRhdXRoZW50aXF1ZSwgbGV2ZWxzID0gYygnVHJ1ZScsICdGYWxzZScpLCBsYWJlbHMgPSBjKCd2cmFpX2JpbGxldCcsICdmYXV4X2JpbGxldCcpKSAjDQoNCiMgb24gY3LDqcOpIGxlcyA0IGxhYmVscyA6DQpjbGFzc2VtZW50X2ttID0gZmFjdG9yKHBhc3RlKGttZWFucywgZGF0YSRhdXRoZW50aXF1ZSwgc2VwID0gJyAtICcpKQ0KY2xhc3NlbWVudF9oayA9IGZhY3RvcihwYXN0ZShjbHVzdGVycywgZGF0YSRhdXRoZW50aXF1ZSwgc2VwID0gJyAtICcpKQ0KY2xhc3NlbWVudF9oY3BjID0gZmFjdG9yKHBhc3RlKGhjcGNfY2x1c3RlcnMsIGRhdGEkYXV0aGVudGlxdWUsIHNlcCA9ICcgLSAnKSkNCg0KDQpmaWdfa20gPSBmdml6X3BjYV9pbmQoZGF0YV9udW1fcGNhLCANCiAgICAgICAgICAgICBnZW9tPWMoJ3BvaW50JyksDQogICAgICAgICAgICAgcG9pbnRzaGFwZSA9IDE5LA0KICAgICAgICAgICAgIGhhYmlsbGFnZSA9IGNsYXNzZW1lbnRfa20sDQogICAgICAgICAgICAgcGFsZXR0ZSA9IGMoJyNCMjAwMDAnLCAgIyAxIC0gZmF1eCBiaWxsZXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAnI0IyNjAwMCcsICAjIDEgLSB2cmFpIGJpbGxldCANCiAgICAgICAgICAgICAgICAgICAgICAgICAnIzAwQjJBMCcsICAjIDIgLSBmYXV4IGJpbGxldA0KICAgICAgICAgICAgICAgICAgICAgICAgICcjMDBCMjMzJyksICMgMiAtIHZyYWkgYmlsbGV0DQogICAgICAgICAgICAgYWxwaGEuaW5kPSJjb3MyIiwNCiAgICAgICAgICAgICBlbGxpcHNlLnR5cGUgPSAibm9ybSIsIA0KICAgICAgICAgICAgIG1lYW4ucG9pbnQgPSBGQUxTRSwNCiAgICAgICAgICAgICBsZWdlbmQudGl0bGUgPSAiTMOpZ2VuZGUiLA0KICAgICAgICAgICAgIHRpdGxlID0gIlZpc3VhbGlzYXRpb24gay1tZWFucyINCikNCg0KZmlnX2hrID0gZnZpel9wY2FfaW5kKGRhdGFfbnVtX3BjYSwgDQogICAgICAgICAgICAgZ2VvbT1jKCdwb2ludCcpLA0KICAgICAgICAgICAgIHBvaW50c2hhcGUgPSAxOSwNCiAgICAgICAgICAgICBoYWJpbGxhZ2UgPSBjbGFzc2VtZW50X2hrLA0KICAgICAgICAgICAgIHBhbGV0dGUgPSBjKCcjQjIwMDAwJywgICMgMSAtIGZhdXggYmlsbGV0DQogICAgICAgICAgICAgICAgICAgICAgICAgJyNCMjYwMDAnLCAgIyAxIC0gdnJhaSBiaWxsZXQgDQogICAgICAgICAgICAgICAgICAgICAgICAgJyMwMEIyQTAnLCAgIyAyIC0gZmF1eCBiaWxsZXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAnIzAwQjIzMycpLCAjIDIgLSB2cmFpIGJpbGxldA0KICAgICAgICAgICAgIGFscGhhLmluZD0iY29zMiIsDQogICAgICAgICAgICAgZWxsaXBzZS50eXBlID0gIm5vcm0iLCANCiAgICAgICAgICAgICBtZWFuLnBvaW50ID0gRkFMU0UsDQogICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gIkzDqWdlbmRlIiwNCiAgICAgICAgICAgICB0aXRsZSA9ICJWaXN1YWxpc2F0aW9uIGNsYXNzLiBoacOpcmFyY2hpcXVlIg0KKQ0KDQpmaWdfaGNwYyA9IGZ2aXpfcGNhX2luZChkYXRhX251bV9wY2EsIA0KICAgICAgICAgICAgIGdlb209YygncG9pbnQnKSwNCiAgICAgICAgICAgICBwb2ludHNoYXBlID0gMTksDQogICAgICAgICAgICAgaGFiaWxsYWdlID0gY2xhc3NlbWVudF9oY3BjLA0KICAgICAgICAgICAgIHBhbGV0dGUgPSBjKCcjQjIwMDAwJywgICMgMSAtIGZhdXggYmlsbGV0DQogICAgICAgICAgICAgICAgICAgICAgICAgJyNCMjYwMDAnLCAgIyAxIC0gdnJhaSBiaWxsZXQgDQogICAgICAgICAgICAgICAgICAgICAgICAgJyMwMEIyQTAnLCAgIyAyIC0gZmF1eCBiaWxsZXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAnIzAwQjIzMycpLCAjIDIgLSB2cmFpIGJpbGxldA0KICAgICAgICAgICAgIGFscGhhLmluZD0iY29zMiIsDQogICAgICAgICAgICAgZWxsaXBzZS50eXBlID0gIm5vcm0iLCANCiAgICAgICAgICAgICBtZWFuLnBvaW50ID0gRkFMU0UsDQogICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gIkzDqWdlbmRlIiwNCiAgICAgICAgICAgICB0aXRsZSA9ICJWaXN1YWxpc2F0aW9uIEhDUEMiDQopDQoNCmZpZ19rbQ0KZmlnX2hrDQpmaWdfaGNwYw0KYGBgDQpgYGB7ciBOZXR0b3lhZ2UgMn0NCnJtKGRhdGFfaGssIGRhdGFfa21lYW5zLCBkYXRhX251bV9wY2FfaW5kLCBkYXRhX251bV9wY2FfdmFyLCBkYXRhX3NjYWxlLCBkZW5kcl9jb2xvciwgZWlnX3ZhbCwgZmlnX2hjcGMsIGZpZ19oaywgZmlnX2ttLCBoY3BjX2NsdXN0LCByZXMua20sIHRhYmxlX3Rlc3Rfa20sIHRlc3RfY29uZnUsIHZhciwgYXV0aF9rbWVhbnMsIGNsYXNzZW1lbnRfaGNwYywgY2xhc3NlbWVudF9oaywgY2xhc3NlbWVudF9rbSwgY2x1c3RlcnMsIGNvbmZ1X2hrLCBjdGFibGUsIGhjcGNfY2x1c3RlcnMsIGttZWFucywgcmVzX2ttLCB0YWJsZV9oY3BjLCBoa19yZXN1bHRzLCBkYXRhX3Rlc3QpDQoNCnJtKGRhdGEsIGRhdGFfbnVtLCBkYXRhX251bV9wY2EsIGRhdGFfb2spDQpgYGANCg0KDQojTWlzc2lvbiAzIC0gTW9kw6lsaXNleiBsZXMgZG9ubsOpZXMgw6AgbCdhaWRlIGQndW5lIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUNCg0KYGBge3IgRG9ubsOpZXMgJiBsaWJyYWlyaWVzLCBpbmNsdWRlPUZBTFNFfQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoTUFTUykNCmxpYnJhcnkocFJPQykNCmRhdGEgPSByZWFkLmNzdihmaWxlID0gIm5vdGVzLmNzdiIsIHNlcD0nLCcsIGRlYyA9ICcuJykNCmBgYA0KDQojIDMuMiBFY2hhbnRpbGxvbm5hZ2UgJiBmcsOpcXVlbmNlcyANCg0KYGBge3IgZnLDqXF1ZW5jZXMgcmVsYXRpdmVzIGRlcyBjbGFzc2VzIGRhbnMgbGUgamV1IGRlIGRvbm7DqWVzfQ0KcHJpbnQocHJvcC50YWJsZSh0YWJsZShkYXRhJGlzX2dlbnVpbmUpKSkNCmBgYA0KDQpgYGB7ciBpbmRleCBkZXMgaW5kaXZpZHVzIGVuIGFwcHJlbnRpc3NhZ2V9DQpzZXQuc2VlZCgxMDApDQp0cmFpbkluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0YSRpc19nZW51aW5lLHA9MC44LGxpc3Q9RikNCnByaW50KGxlbmd0aCh0cmFpbkluZGV4KSkNCmBgYA0KDQpgYGB7ciBJbnRlcnByw6l0YXRpb24gc8OpcGFyYXRpb24sIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpPbiBhIHVuZSBsaXN0ZSBkZSAxMzYgaW5kZXggZGUgYmlsbGV0cyBxdWkgY29uc3RpdHVlbnQgbCfDqWNoYW50aWxsb24gZCdlbnRyYcOubmVtZW50IGR1IG1vZMOobGUsIHNvaXQgODAlIGRlcyBkb25uw6llcyB0b3RhbGVzLiBPbiB1dGlsaXNlIGNlIHZlY3RldXIgcG91ciBwYXJ0aXRpb25uZXIgbGUgZGF0YSBmcmFtZQ0KYGBgDQoNCmBgYHtyIENvbXBvc2l0aW9uIGRlcyBlbnNlbWJsZXN9DQojUG91ciBsJ2Vuc2VtYmxlIGQnYXBwcmVudGlzc2FnZQ0KZGF0YVRyYWluIDwtIGRhdGFbdHJhaW5JbmRleCxdDQpwcmludChkaW0oZGF0YVRyYWluKSkNCg0KI1BvdXIgbCfDqWNoYW50aWxsb24gdGVzdCwgdmlhIGwnaW5kacOnYWdlIG7DqWdhdGlmDQpkYXRhVGVzdCA8LSBkYXRhWy10cmFpbkluZGV4LF0NCnByaW50KGRpbShkYXRhVGVzdCkpDQpgYGANCg0KYGBge3IgQ29uZmlybWF0aW9uIMOpY2hhbnRpbGxvbm5hZ2UsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpPbiBhIGJpZW4gMzQrMTM2ID0gMTcwIG9ic2VydmF0aW9ucw0KYGBgDQoNCmBgYHtyIERpc3RyaWJ0aW9uIGRlcyBjbGFzc2VzfQ0KI2Zyw6lxdWVuY2VzIGFic29sdWVzIGRlcyBjbGFzc2VzIC0gw6ljaC4gZCdhcHByZW50aXNzYWdlDQpwcmludCh0YWJsZShkYXRhVHJhaW4kaXNfZ2VudWluZSkpDQoNCiNmcsOpcXVlbmNlcyByZWxhdGl2ZXMgZGVzIGNsYXNzZXMgZGFucyBsJ8OpY2guIGQnYXBwcmVudGlzc2FnZQ0KcHJpbnQocHJvcC50YWJsZSh0YWJsZShkYXRhVHJhaW4kaXNfZ2VudWluZSkpKQ0KDQojZnLDqXF1ZW5jZXMgYWJzb2x1ZXMgZGVzIGNsYXNzZXMgLSDDqWNoLiBkJ3Rlc3QNCnByaW50KHRhYmxlKGRhdGFUZXN0JGlzX2dlbnVpbmUpKQ0KDQojZGlzdHJpYnV0aW9uIGRlcyBjbGFzc2VzIGRhbnMgbCfDqWNoLiB0ZXN0DQpwcmludChwcm9wLnRhYmxlKHRhYmxlKGRhdGFUZXN0JGlzX2dlbnVpbmUpKSkNCg0KIyAtLT4gTGVzIGZyw6lxdWVuY2VzIHJlbGF0aXZlcyBzb250IGNvbmZvcm1lcyBhdmVjIGxhIGRpc3RyaWJ1dGlvbiBpbml0aWFsZSANCmBgYA0KDQojIDMuMyBNb2TDqWxpc2F0aW9uIA0KDQpgYGB7ciBQYXJhbcOodHJlcyAmIGTDqWZpbml0aW9uIGR1IG1vZMOobGV9DQojcGFyYW3DqHRyZSBkdSBwcm9jZXNzdSBkJ2FwcHJlbnRpc3NhZ2UgOiBvbiBsYWlzc2UgdG91dCBwYXIgZMOpZmF1dA0KZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kPSJub25lIikNCiNhcHByZW50aXNzYWdlIC0gcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZQ0KbV9sciA8LSB0cmFpbihpc19nZW51aW5lIH4gLiwgZGF0YSA9IGRhdGFUcmFpbixtZXRob2Q9ImdsbSIsdHJDb250cm9sPWZpdENvbnRyb2wpDQpwcmludChtX2xyKQ0KYGBgDQoNCmBgYHtyIFNvcnRpZSBkdSBtb2TDqGxlICYgY29lZmZpY2llbnRzfQ0KI21vZMOobGUgc291cy1qYWNlbnQgaXNzdSBkZSB0cmFpbg0KI2NvZWZmaWNpZW50cyBkZSBsYSByw6lncmVzc2lvbiBsb2dpc3RpcXVlDQpwcmludChtX2xyJGZpbmFsTW9kZWwpDQoNCiNBSUMgZHUgbW9kw6hsZSDDoCAxNA0KYGBgDQoNCiMgMy40IFByw6lkaWN0aW9uDQoNCmBgYHtyIHByw6lkaWN0aW9ufQ0KcHJlZCA8LSBwcmVkaWN0KG1fbHIsbmV3ZGF0YT1kYXRhVGVzdCkNCiNkaXN0cmlidXRpb24gZGVzIGNsYXNzZXMgcHLDqWRpdGVzDQpwcmludCh0YWJsZShwcmVkKSkNCg0KI09uIGEgMTMgcHLDqWRpY3Rpb24gZGUgZmF1eCBiaWxsZXRzIGV0IDIxIGRlIHZyYWlzIGJpbGxldHMsIMOgIHbDqXJpZmllciBwYXIgbGEgc3VpdGUNCmBgYA0KDQojIDMuNSBNYXRyaWNlIGRlIGNvbmZ1c2lvbiBldCBpbmRpY2F0ZXVycyBkJ8OpdmFsdWF0aW9uDQoNCmBgYHtyIE1hdHJpY2UgZGUgY29uZnVzaW9ufQ0KbWF0IDwtIGNvbmZ1c2lvbk1hdHJpeChkYXRhPXByZWQscmVmZXJlbmNlPWFzLmZhY3RvcihkYXRhVGVzdCRpc19nZW51aW5lKSxwb3NpdGl2ZT0iVHJ1ZSIpDQpwcmludChtYXQpDQoNCiNUYXV4IGRlIHN1Y2PDqHMgKGFjY3VyYWN5KSDDoCA5NyUgISBMJ2ludGVydmFsbGUgZGUgY29uZmlhbmNlIMOgIDk1JSBlc3QgZm91cm5pIG1haXMgbCfDqWNoYW50aWxsb24gw6l0YW50IGZhaWJsZSwgbCdpbmNlcnRpdHVkZSBwZXJzaXN0ZS4gDQojT24gY29uc3RhdGUgdW4gZmF1eCBwb3NpdGlmLg0KI0xhIHNlbnNpYmlsaXTDqSDDoCBsYSBjbGFzc2UgcG9zaXRpdmUgKFRydWUpIHMnw6l0YWJsaXQgw6AgMTAwJSAoMjAvKDIwKzApICENCmBgYA0KDQpgYGB7ciBJbmRpY2F0ZXVycyBwYXIgY2xhc3NlfQ0KcHJpbnQobWF0JGJ5Q2xhc3MpDQojIFByw6ljaXNpb24gc3VyIGwnw6ljaGFudGlsbG9uIGRlIHRlc3QgOiA5NSUgIQ0KYGBgDQoNCiMgMy42IEV2YWx1YXRpb24gZHUgbW9kw6hsZQ0KDQojIDMuNi4xIENvdXJiZSBMSUZUDQoNCmBgYHtyIEV4cGxpY2F0aW9ucyBjb3VyYmUgTElGVCwgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NClV0aWxpc8OpZSBwb3VyIG1lc3VyZXIgbOKAmWVmZmljYWNpdMOpIGTigJl1biBjaWJsYWdlIChzY29yaW5nKSANClBvdXIgbGEgY29uc3RydWlyZSwgZW4gc3VzIGRlcyBjbGFzc2VzIG9ic2VydsOpZXMsIG5vdXMgYXZvbnMgYmVzb2luIGRlIGxhIHByb2JhYmlsaXTDqSAoc2NvcmUpIGTigJnDqnRyZSBkZSBsYSBjbGFzc2UgcG9zaXRpdmUgZm91cm5pZSBwYXIgbGUgbW9kw6hsZSBbUChpc19nZW51aW5lID0gVHJ1ZSAvZGVzY3JpcHRpb24pXS4NCg0KTGEgY291cmJlIGVzdCBwcm9jaGUgZGUgbGEgbGltaXRlIHRow6lvcmlxdWUgKGF0dGVpbnRlIGxvcnNxdWUgdG91cyBsZXMgaXNfZ2VudWluZSA9IFRydWUgc2Ugdm9pZW50IGF0dHJpYnVlciB1biBzY29yZSBwbHVzIMOpbGV2w6kgcXVlIGxlcyBpc19nZW51aW5lID0gRmFsc2UpLiBOb3RyZSBjaWJsYWdlIGVzdCBk4oCZZXhjZWxsZW50ZSBxdWFsaXTDqSAodHJvcCBib25uZSBxdWFsaXTDqSwgbGEgY291cmJlIHNlIGNvbmZvbmQgYXZlYyBsYSBsaW1pdGUgdGjDqW9yaXF1ZSAtPiBsZXMgZG9ubsOpZXMgc29udCBwYXJ0aWN1bGnDqHJlcykuDQpgYGANCg0KYGBge3IgU2NvcmluZyAmIHBsb3QgZGUgbGEgY291cmJlIExJRlR9DQojc2NvcmUgZGVzIGluZGl2aWR1cyBwb3NpdGlmcw0Kc2NvcmUgPC0gcHJlZGljdChtX2xyLGRhdGFUZXN0LHR5cGU9InByb2IiKVssIlRydWUiXQ0KI3ByaW50KHF1YW50aWxlKHNjb3JlKSkNCg0KI09uIGNyw6llIHVuIGRhdGEgZnJhbWUgcmVncm91cGFudCBsZXMgY2xhc3NlcyBvYnNlcnbDqWVzIGV0IGxlcyBzY29yZXMNCmxpZnRkYXRhIDwtIGRhdGEuZnJhbWUoY2xhc3NlPWFzLmZhY3RvcihkYXRhVGVzdCRpc19nZW51aW5lKSkNCmxpZnRkYXRhJHNjb3JlIDwtIHNjb3JlDQoNCiNvYmpldCBsaWZ0DQpsaWZ0X29iaiA8LSBsaWZ0KGNsYXNzZSB+IHNjb3JlLCBkYXRhPWxpZnRkYXRhLCBjbGFzcz0iVHJ1ZSIpDQpwcmludChsaWZ0X29iaikgI0xhIGZvbmN0aW9uIHByaW50KCkgaW5kaXF1ZSBzZXVsZW1lbnQgbGEgcHJvcG9ydGlvbiBkZXMgb2JzZXJ2YXRpb25zIHBvc2l0aXZlcyAoaXNfZ2VudWluZSA9IFRydWUpLg0KDQojYWZmaWNoYWdlIGRlIGxhIGNvdXJiZSBsaWZ0DQpwbG90KGxpZnRfb2JqKQ0KYGBgDQoNCiMgMy42LjIgQ291YmUgUk9DDQoNCmBgYHtyIEV4cGxpY2F0aW9ucyBjb3VyYmUgUk9DLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KRWxsZSB2aXNlIMOgIG1lc3VyZXIgbGEgcXVhbGl0w6kgZOKAmXVuIG1vZMOobGUgZW4gc+KAmWFmZnJhbmNoaXNzYW50IGRlcyBjb8O7dHMgZGUgbWF1dmFpc2UgYWZmZWN0YXRpb24gZXQgZGUgbGEgcmVwcsOpc2VudGF0aXZpdMOpIGRlIGzigJnDqWNoYW50aWxsb24gdXRpbGlzw6kgKGxlcyBwcm9wb3J0aW9ucyBkZXMgY2xhc3NlcyBkYW5zIGzigJnDqWNoYW50aWxsb24gcGV1dCDDqnRyZSBkaWZmw6lyZW50IGRlIGNlbHVpIGRlIGxhIHBvcHVsYXRpb24pLiANCkNhIG5lIHNlcmEgcGFzIGxlIGNhcyBpY2ksIHB1aXNxdSdvbiBhIHbDqXJpZmnDqSBsYSBwcm9wb3J0aW9uIGVuIGFtb250LCBldCBxdWUgbCfDqWNoYW50aWxsb25uYWdlIGF2ZWMgQ2FyZXQgcGVybWV0IGRlIGNvbnNlcnZlciBsZXMgcHJvcG9ydGlvbnMNCg0KRXZpZGVtbWVudCwgbGEgY291cmJlIGVzdCBkcm9pdGUgZXQgbCdhaXJlIHNvdXMgbGEgY291cmJlIGVzdCBkZSAxIDogYXVjdW5lIGRpZmbDqXJlbmNlIGRlIHByb3BvcnRpb24gZGUgdnJhaXMgLyBmYXV4IGJpbGxldHMgZW50cmUgbCfDqWNoYW50aWxsb24gZXQgbGVzIGRvbm7DqWVzIGQnZW5zZW1ibGUuDQpMJ2FpcmUgc291cyBsYSBjb3VyYmUgZXN0IMOpZ2FsZSDDoCAxIDogbGUgbW9kw6hsZSBhIHVuZSBjYXBhY2l0w6kgZGlzY3JpbWluYXRvaXJlIG1heGltYWxlIChwcm9iYWJpbGl0w6kgcXVlIHBhcm1pIGRldXggc3VqZXRzIGNob2lzaXMgYXUgaGFzYXJkLCBsYSB2YWxldXIgZHUgbWFycXVldXIgw6l0dWRpw6kgc29pdCBwbHVzIMOpbGV2w6llIHBvdXIgdW4gdnJhaSBiaWxsZXQgcXVlIHBvdXIgdW4gZmF1eCBiaWxsZXQgKQ0KYGBgDQoNCmBgYHtyIFBsb3QgZGUgbGEgY291cmJlIFJPQ30NCiNTY29yZQ0Kc2NvcmUgPC0gcHJlZGljdChtX2xyLGRhdGFUZXN0LHR5cGU9InByb2IiKVssIlRydWUiXQ0KI29iamV0IHJvYw0Kcm9jX29iaiA8LSByb2MoZGF0YVRlc3QkaXNfZ2VudWluZT09IlRydWUiLHNjb3JlKQ0KI3Bsb3QgZGUgbCdvYmpldCByb2MNCnBsb3QoMS1yb2Nfb2JqJHNwZWNpZmljaXRpZXMsIHJvY19vYmokc2Vuc2l0aXZpdGllcywgdHlwZT0ibCIpDQphYmxpbmUoMCwxKQ0KDQojUGV1dC3DqnRyZSB1biBwcm9ibMOobWUgYXUgbml2ZWF1IGRlIGwnZW5jb2RhZ2UgdHJ1ZS9mYWxzZSAtLT4gYXNzYXllciBhdmVjIGRlcyAwLzEgcG91ciB2b2lyDQpgYGANCg0KYGBge3IgQWlyZSBzb3VzIGxhIGNvdXJiZX0NCnByaW50KHJvY19vYmokYXVjKQ0KYGBgDQoNCg0KIyAzLjcgUsOpLcOpY2hhbnRpbGxvbm5hZ2UNCg0KYGBge3IgRXhwbGljYXRpb25zIHJlZWNoYW50aWxsb25uYWdlLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KTGUgc2Now6ltYSBhcHByZW50aXNzYWdlLXRlc3QgbuKAmWVzdCBwYXMgZm9yY8OpbWVudCBsZSBwbHVzIGFkYXB0w6kgYXV4IGJhc2VzIGRlIHRhaWxsZSByw6lkdWl0ZS4NCklsIGVzdCBwbHVzIGp1ZGljaWV1eCBkYW5zIGNlIGNhcyBk4oCZdXRpbGlzZXIgbGEgdG90YWxpdMOpIGRlIGxhIGJhc2UgcG91ciDDqWxhYm9yZXIgbGUgbW9kw6hsZSBwcsOpZGljdGlmLCBwdWlzIGRlIHBhc3NlciBwYXIgdW5lIHRlY2huaXF1ZSBkZSByw6nDqWNoYW50aWxsb25uYWdlIHBvdXIgZW4gw6l2YWx1ZXIgbGVzIHBlcmZvcm1hbmNlcyAodHlwaXF1ZW1lbnQgbGEgdmFsaWRhdGlvbiBjcm9pc8OpZSBvdSBsZSBib290c3RyYXApLg0KDQpOb3VzIGNvbnNlcnZvbnMgbm90cmUgw6ljaGFudGlsbG9uIGTigJlhcHByZW50aXNzYWdlIHRlbCBxdWVsLCBldCBub3VzIHByb2PDqWRvbnMgcGFyIHZhbGlkYXRpb24gY3JvaXPDqWUgcG91ciBlbiBlc3RpbWVyIGxlcw0KcGVyZm9ybWFuY2VzLiBOb3VzIHZlcnJvbnMgc2kgbGUgdGF1eCBvYnRlbnUgZXN0IGNvbmZvcm1lIMOgIGNlbHVpIG1lc3Vyw6kgc3VyIGzigJnDqWNoYW50aWxsb24gdGVzdCBxdWUgbm91cyBhdm9ucyBtaXMgw6AgcGFydC4NCmBgYA0KDQpgYGB7ciBWYWxpZGF0aW9uIGNyb2lzw6llIGF2ZWMgMTAgYmxvY3N9DQojw6l2YWx1YXRpb24gcGFyIHLDqcOpY2hhbnRpbGxvbm5hZ2UgOiB2YWxpZGF0aW9uIGNyb2lzw6llIGF2ZWMgMTAgYmxvY3MsIA0KZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsbnVtYmVyPTEwKSAjMTAgYmxvY3MgDQptX2xyIDwtIHRyYWluKGlzX2dlbnVpbmUgfiAuLCBkYXRhID0gZGF0YVRyYWluLG1ldGhvZD0iZ2xtIix0ckNvbnRyb2w9Zml0Q29udHJvbCkNCnByaW50KG1fbHIpDQpgYGANCg0KYGBge3IgSW50ZXJwcsOpdGF0aW9uIHZhbGlkYXRpb24gY3JvaXPDqWUsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpMZSB0YXV4IGRlIHN1Y2PDqHMgZW4gdmFsaWRhdGlvbiBjcm9pc8OpZSBhbm5vbmPDqSBlc3QgZGUgOTcsOCUsIGNlbHVpIG1lc3Vyw6kgc3VyIGzigJnDqWNoYW50aWxsb24NCnRlc3Qgw6l0YWl0IGRlIDk3LDA2JS4gDQpgYGANCg0KYGBge3IgVGF1eCBkYW5zIGNoYXF1ZSBmb2xkfQ0KcHJpbnQobV9sciRyZXNhbXBsZSkNCmBgYA0KDQojIDMuOCAtIFNlbGVjdGlvbiBkZXMgdmFyaWFibGVzDQoNCiMgMy44LjEgLSBJbXBvcnRhbmNlIGRlcyB2YXJpYWJsZXMNCg0KYGBge3IgSW5mbHVlbmNlIGRlcyB2YXJpYWJsZXN9DQojaW1wb3J0YW5jZSBkZXMgdmFyaWFibGVzIC0gaW50cmluc8OocXVlIGF1IG1vZMOobGUNCnByaW50KHZhckltcChtX2xyKSkNCg0KI1ZhcmlhYmxlIGxhIHBsdXMgaW5mbHVlbnRlIDogbG9uZ3VldXIsIHB1aXMgbWFyZ2VzLCBwdWlzIGhhdXRldXJzLCBwdWlzIGRpYWdvbmFsZQ0KYGBgDQoNCiMgMy44LjIgLSBNw6l0aG9kZSBpbnTDqWdyw6llIGRlIHPDqWxlY3Rpb24NCg0KYGBge3IgU3RlcEFJQ30NCm1fbHJzIDwtIHRyYWluKGlzX2dlbnVpbmUgfiAuLCBkYXRhID0gZGF0YVRyYWluLCBtZXRob2Q9ImdsbVN0ZXBBSUMiLA0KdHJDb250cm9sPXRyYWluQ29udHJvbCgibm9uZSIpKQ0KDQojTW9kw6hsZSBmaW5hbCBvYnRlbnUgOg0KcHJpbnQobV9scnMkZmluYWxNb2RlbCkNCmBgYA0KDQpgYGB7ciBFeHBsaWNhdGlvbiBzdGVwQUlDLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KTGUgbW9kw6hsZSBhIHVuIEFJQyBkZSA2IDogYmllbiBpbmbDqXJpZXVyIGF1IG1vZMOobGUgb3JpZ2luZWwgYXZlYyB0b3V0ZXMgbGVzIHZhcmlhYmxlcyBleHBsaWNhdGl2ZXMuIExlIGNyaXTDqHJlIGRlIHBhcmNpbW9uaWUgYW3DqG5lIMOgIGNob2lzaXIgbGUgbW9kw6hsZSBhdmVjIGxlIGNyaXTDqHJlIGQnaW5mb3JtYXRpb24gbGUgcGx1cyBmYWlibGUuIEMnZXN0IHVuIGNvbXByb21pcyBlbnRyZSBsYSBxdWFsaXTDqSBkZSBsJ2FqdXN0ZW1lbnQgZXQgbGEgY29tcGxleGl0w6kgZHUgbW9kw6hsZSwgZW4gcMOpbmFsaXNhbnQgbGVzIG1vZMOobGVzIGF5YW50IHVuIGdyYW5kIG5vbWJyZSBkZSBwYXJhbcOodHJlcywgY2UgcXVpIGxpbWl0ZSBsZXMgZWZmZXRzIGRlIHN1ci1hanVzdGVtZW50IChhdWdtZW50ZXIgbGUgbm9tYnJlIGRlIHBhcmFtw6h0cmUgYW3DqWxpb3JlIG7DqWNlc3NhaXJlbWVudCBsYSBxdWFsaXTDqSBkZSBsJ2FqdXN0ZW1lbnQpLg0KYGBgDQoNCg0KYGBge3IgQXBwbGljYXRpb24gc3VyIGxlcyBkb25uw6llcyB0ZXN0ICYgbWVzdXJlIGRlcyBwZXJmb3JtYW5jZXN9DQojYXBwbGljYXRpb24gc3VyIGxlcyBkb25uw6llcyB0ZXN0ICYgbWVzdXJlIGRlcyBwZXJmb3JtYW5jZXMNCnByaW50KGNvbmZ1c2lvbk1hdHJpeChkYXRhPXByZWRpY3QobV9scnMsbmV3ZGF0YSA9DQpkYXRhVGVzdCkscmVmZXJlbmNlPWFzLmZhY3RvcihkYXRhVGVzdCRpc19nZW51aW5lKSxwb3NpdGl2ZT0iVHJ1ZSIpKQ0KYGBgDQoNCmBgYHtyIEV4cGxpY2F0aW9ucywgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCk9uIGVzdCB0b3Vqb3VycyDDoCA5NyUgZCdhY2N1cmFjeSwgdG91am91cnMgdW4gZmF1eCBuw6lnYXRpZiA6IG1hbGdyw6kgbGEgcsOpZHVjdGlvbiBkdSBub21icmUgZGUgdmFyaWFibGVzIGV4cGxpY2F0aXZlcywgbGUgbW9kw6hsZSBwcsOpZGl0IHRvdWpvdXJzIGF1c3NpIGJpZW4gc3VyIGwnw6ljaGFudGlsbG9uIHRlc3QgISBPbiBwZXV0IGRvbmMgY29uc2VydmVyIGNlIG1vZMOobGUgcG91ciByw6lhbGlzZXIgbGEgcHLDqWRpY3Rpb24uDQpgYGANCg0KDQo=